Download OpenAPI specification:
GLP-1 coaching for telehealth brands, their patients and their prescribers.
TeraRx is the coaching layer for patients on GLP-1 therapy. Telehealth brands switch it on for their patients: an AI Coach on every plan; a health coach and a fitness coach on the coached plan; protocols, meal plans and workout plans built on TeraRx's clinical rules; and clinical flags back to the brand's prescriber. TeraRx never prescribes: it mirrors the dose the brand sends and coaches against it.
| You are | You use | Start with |
|---|---|---|
| A brand's server | Client credentials | Auth, then POST /v1/enrollments |
| A platform partner, for several brands | One client for all your brands, a tenant_id on every token |
Platform partners |
| A brand's app | Patient tokens from token exchange | GET /v1/patient |
| A prescriber system | A clinical client | GET /v1/clinical-flags |
| Receiving events | Webhooks | Webhooks |
All paths start with the major version, /v1. Within a version, changes are additive only: new endpoints, new optional request fields, new response fields, new enum values and new event types. Build clients that ignore fields and values they do not know.
A breaking change means a new version (/v2), announced at least 6 months ahead. During that time the old version's responses carry Deprecation (RFC 9745) and Sunset (RFC 8594) headers with the dates, and both versions run side by side.
application/json; application/merge-patch+json for PATCH). UTF-8.2026-10-26T14:05:00Z). Log dates (log_date) are calendar dates in the patient's time zone.units on the patient is a display preference only.snake_case. In responses, absent and null both mean "no value". In a PATCH (merge patch), null clears a field and an absent field is left unchanged.Every response has an X-Request-Id header, errors included. Ours are req_ followed by a UUIDv7. Send your own X-Request-Id to have it echoed, which makes logs easy to join: a UUID, or req_ followed by up to 60 letters, digits, _ or -. Any other value is replaced with a new id. Quote it when you write to support@hellotera.com.
Every 4xx and 5xx response is RFC 9457 problem details (application/problem+json):
{
"type": "about:blank",
"title": "Unprocessable Content",
"status": 422,
"detail": "1 field is invalid.",
"code": "validation_failed",
"request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b",
"errors": [{ "pointer": "/patient/state_code", "code": "invalid_format", "detail": "Must be a two-letter USPS state code." }]
}
Branch on code, which is stable; title and detail may change. Errors never contain patient data: no names, no values you sent, no free text. A resource from another brand or another patient returns 404 exactly like one that does not exist.
| Status | Common code values |
|---|---|
| 400 | malformed_request, idempotency_key_missing |
| 401 | token_missing, token_expired, token_invalid, client_revoked |
| 403 | wrong_caller_type (a token of another kind, such as a patient token on a brand-server endpoint), insufficient_scope, plan_not_eligible (a coached-plan endpoint on ai_only), treatment_managed_by_brand |
| 404 | <resource>_not_found, for example enrollment_not_found |
| 409 | invalid_status_transition, idempotency_key_in_use, enrollment_exists, external_order_exists, flag_closed, ai_paused |
| 410 | export_expired, event_too_old |
| 422 | validation_failed, idempotency_key_reused, plan_not_offered, unknown_medication, unknown_legal_document |
| 429 | rate_limited, daily_message_cap_reached |
| 503 | ai_unavailable, service_unavailable |
Each operation lists the codes specific to it.
Every POST that creates something (answers 201) requires an Idempotency-Key header of 8 to 255 characters (a UUID is a good choice); without it the request is refused with 400 idempotency_key_missing. Most other POST actions accept one; each operation in the reference lists the header when it does.
Idempotent-Replayed: true, and creates nothing new.422 idempotency_key_reused.409 idempotency_key_in_use; retry shortly.Keys are scoped to the caller: your client, or the patient for patient tokens. Logs your server sends also carry your own external_id, which dedupes them permanently.
Lists that can grow are cursor-paginated: ?limit= (1 to 100, default 25) and ?cursor=. The response is { "data": [...], "next_cursor": "..." }; pass next_cursor back as cursor until it is null. Cursors are opaque and expire after 24 hours. Short, bounded lists return { "data": [...] } without a cursor.
Limits are per client (600 requests per minute by default, set per contract), per brand for a platform partner's client (each brand under it has its own limit), and per patient for patient tokens. Every response carries:
| Header | Meaning |
|---|---|
RateLimit-Limit |
Requests allowed per window |
RateLimit-Remaining |
Requests left |
RateLimit-Reset |
Seconds until the window resets |
Over the limit you get 429 with Retry-After in seconds. The AI Coach also has a daily message cap per patient, set on the brand's plan (429 daily_message_cap_reached).
Patient endpoints say which plans they work on (the x-plans extension, and "Plans" at the end of each description). ai_health_fitness is the coached plan; ai_only has the AI Coach and no coaches, and its escalations go to the brand's prescriber as clinical flags. Calling a coached-plan endpoint for an ai_only patient returns 403 plan_not_eligible.
Coaching is not emergency care. Wherever a patient can type (care messages, the AI Coach, symptom logging), show this guidance: in an emergency call 911; in a mental health crisis call or text 988. When a symptom log comes back with is_red_flag: true, or an AI Coach reply with show_emergency_guidance: true, show it before anything else.
| Environment | Base URL | Data |
|---|---|---|
| Sandbox (integration) | https://sandbox.rx.tera.health |
Synthetic data only; nothing is billed |
| Local mock | http://localhost:4010 |
The examples in this spec. See Run the mock locally |
| Production | https://api.rx.tera.health |
Real patients |
Each environment has its own clients, keys and webhook secret.
Three kinds of caller use this API, each with its own token. The tokens are not interchangeable: a brand-server token cannot call patient endpoints, and a patient token cannot call brand-server or clinical endpoints. Coaches use the TeraRx portal, which is not part of this contract.
| Caller | Security scheme | How it gets a token | Lifetime |
|---|---|---|---|
| Brand server | brandServer |
Client credentials with its own client_id and secret |
1 hour |
| Patient (the brand's app) | patientToken |
Token exchange of an assertion the brand's server signed | 15 minutes |
| Prescriber system (the brand's clinician tool) | clinicalClient |
Client credentials with a separate clinical client | 1 hour |
Each operation lists the scheme and the scope it needs. All tokens go in Authorization: Bearer <token>.
A platform partner that hosts several brands holds one client for all of them and names the brand on every token request, so each token is still for one brand. See Platform partners.
TeraRx issues one OAuth client per brand and environment (integration, production). The secret is shown once; TeraRx keeps only its hash. Keep it on your server, never in an app. The integration client works against the sandbox only; the production API answers it with unauthorized_client.
| Problem | code |
HTTP |
|---|---|---|
| Unknown client or wrong secret (the same answer for both) | invalid_client |
401 |
| The client was revoked | client_revoked |
401, also for tokens it already holds, within a minute |
A scope the client does not hold |
invalid_scope |
400 |
A grant_type other than the two below |
unsupported_grant_type |
400 |
| The integration client on the production API | unauthorized_client |
400 |
A partner client sent no tenant_id |
tenant_required |
400 |
A tenant_id this client may not act for, or a brand that is not live |
tenant_not_permitted |
400 |
POST /v1/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=enrollments:read%20enrollments:write%20logs:write
| Scope | Allows |
|---|---|
enrollments:read |
Read enrollment status, stage and risk level, the medication list, the treatment mirror and consent records |
enrollments:write |
Enroll, update the patient's details, change plan, pause, resume, cancel, ask the AI Coach to reach out, update the treatment mirror, record consent proof |
logs:write |
Send logs your app already recorded |
events:read |
Replay events |
reports:read |
The aggregate brand report |
data_requests:read, data_requests:write |
Read data requests and download exports; create data requests and decide amendments |
patients:token_exchange |
Your signing keys may mint patient tokens (see below) |
tenants:read |
List the brands your client may act for |
A brand server reads status and ids only. It never reads clinical content: messages, form answers, protocol text and log values stay with the patient and the care team.
There are no patient passwords in the API. Your server vouches for the patient by signing a short-lived JWT with your own private key; TeraRx checks it against the public keys you registered (a JWKS URL or a JWKS document) and returns a patient token. TeraRx never holds a key that can mint patient sessions, and you can rotate keys yourself by publishing a new key in your JWKS.
POST /v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<assertion>
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
access_token is valid for 15 minutes for that one patient and enrollment. There is no refresh token: sign a new assertion when it expires.A JWT (RFC 7519) signed with ES256, RS256 or EdDSA. The header names the key with kid, which must be in your registered JWKS.
| Claim | Required | Value |
|---|---|---|
iss |
Yes | Your issuer, exactly as registered with TeraRx at onboarding |
sub |
Yes | Your patient id: the external_patient_id you used on POST /v1/enrollments |
aud |
Yes | The TeraRx API audience for the environment, given at onboarding |
iat |
Yes | Issued at, in Unix seconds |
exp |
Yes | Expiry, at most 5 minutes after iat |
jti |
Yes | A unique id. Each assertion can be exchanged once |
tenant_id |
Platform partners only | The brand the patient is enrolled with. A brand's own assertions may omit it |
TeraRx allows 30 seconds of clock skew on iat and exp. The assertion carries no names or health data.
If you registered a JWKS URL, TeraRx caches it for 10 minutes and fetches it again as soon as an assertion names a kid it has not seen (at most every 30 seconds). To rotate, publish the new key next to the old one, start signing with it, then remove the old one.
| Problem | code |
HTTP |
|---|---|---|
| Signature does not match a registered key | assertion_signature_invalid |
400 |
exp has passed |
assertion_expired |
400 |
exp is more than 5 minutes after iat |
assertion_lifetime_too_long |
400 |
jti already used |
assertion_replayed |
400 |
Wrong aud |
assertion_audience_invalid |
400 |
iss not registered |
assertion_issuer_unknown |
400 |
A required claim is missing or malformed, or iat is in the future |
assertion_invalid |
400 |
| A partner's assertion names a brand the partner may not act for, or one that is not live | tenant_not_permitted |
400 |
No enrollment for sub with this brand |
patient_not_found |
400 |
The enrollment is ineligible, cancelled or completed |
enrollment_not_active |
400 |
The client registered for your issuer lacks patients:token_exchange |
token_exchange_not_allowed |
400 |
| The client was revoked | client_revoked |
401, also for the patient tokens it already exchanged, within a minute |
| Too many requests for the client registered for your issuer | rate_limited |
429 |
TeraRx could not fetch your JWKS URL, or could not check the jti; retry after Retry-After seconds |
service_unavailable |
503 |
The 400 and 401 problems also carry the RFC 6749 error field (invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope, invalid_target) for OAuth libraries.
Your prescriber system gets its own OAuth client, separate from the brand server's, with clinical:read (read flags) and clinical:write (acknowledge and resolve). It authenticates with client credentials like a brand server.
A clinical client acts for many prescribers, so every clinical write names the individual in the request body:
{
"on_behalf_of": { "npi": "1234567893", "prescriber_id": "dr-00417", "name": "Dr. Alex Morgan, NP" }
}
name is required, plus at least one of npi or prescriber_id. A write without on_behalf_of, or with an NPI whose check digit is wrong, is refused with 422 validation_failed and the field's pointer. TeraRx stores the prescriber and the client that sent it on the flag, shows the name to coaches, and records it in the audit log.
Client credentials (brand servers and prescriber systems): authenticate with HTTP Basic
(client_id:client_secret) and send grant_type=client_credentials. The token lasts 1 hour and carries the
client's scopes, or the subset you ask for in scope.
Token exchange (patients): send the brand-signed assertion as subject_token. No client secret is needed,
so the brand's app can make this call itself with an assertion its server just signed. The result is a
15-minute patient token bound to the one enrollment the assertion names. There is no refresh token; sign a
new assertion when it expires. Patient tokens are issued for enrollments that are pending_screening,
active or paused.
An integration client cannot get tokens from the production API (unauthorized_client).
Platform partners that act for several brands name the brand with tenant_id on the client-credentials
request, and in a tenant_id claim in patient assertions. Every token is for one brand. See "Platform
partners" in the Tenants tag.
Error codes: invalid_client, client_revoked, unauthorized_client, unsupported_grant_type, invalid_scope,
tenant_required, tenant_not_permitted, token_exchange_not_allowed, assertion_signature_invalid,
assertion_expired, assertion_lifetime_too_long, assertion_replayed, assertion_audience_invalid,
assertion_issuer_unknown, assertion_invalid, patient_not_found, enrollment_not_active.
| grant_type required | string Value: "client_credentials" |
| scope | string Space-separated scopes. Omit to get every scope the client holds. Asking for one it does not hold fails with |
| tenant_id | string <uuid> The brand this token is for. Required for a platform partner's client, which acts for several brands and
gets one token per brand ( |
| client_id | string Send only when not using HTTP Basic. |
| client_secret | string Send only when not using HTTP Basic. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Cache-Control | string Example: "no-store" Always |
| access_token required | string Opaque to callers. Do not parse it. |
| token_type required | string Value: "Bearer" |
| expires_in required | integer Seconds. 3600 for client tokens, 900 for patient tokens. |
| scope | string Space-separated scopes granted. Absent on patient tokens. |
| issued_token_type | string Token exchange only (RFC 8693). Value: "urn:ietf:params:oauth:token-type:access_token" |
grant_type=client_credentials&scope=enrollments%3Aread%20enrollments%3Awrite%20logs%3Awrite
{- "access_token": "trx_at_example_not_a_real_token",
- "token_type": "Bearer",
- "expires_in": 3600,
- "scope": "enrollments:read enrollments:write logs:write"
}A platform partner hosts several telehealth brands and switches TeraRx on for them. The partner integrates once, with one set of credentials for all its brands. A brand that integrates directly keeps its own client and needs none of this.
Each brand stays its own tenant. Its patients, enrollments, logs and reports are separate from every other brand's, including brands under the same partner. Nothing is matched or shared across brands: a person enrolled with two of your brands is two patients.
TeraRx creates each brand at onboarding, with its plans, prices, retention and coaching rules, and gives you its tenant_id. You get:
| What | Count | Used for |
|---|---|---|
| A brand-server client | One per environment, for all your brands | Client credentials, with the scopes your brands need, plus tenants:read |
| Signing keys for patient assertions | One JWKS URL (or JWKS document) and one issuer | Token exchange for every brand's patients |
| A clinical client | One per environment, if you run the prescriber tooling for your brands | Clinical flags, one token per brand named with tenant_id, as for the brand-server client. A brand whose prescriber system is run by someone else keeps its own clinical client |
| A webhook endpoint and secret | One, for all your brands | A brand may have its own endpoint instead |
Every token is for one brand. Name the brand with tenant_id when you ask for a token:
POST /v1/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&tenant_id=a0000000-0000-4000-8000-000000000002&scope=enrollments:read%20enrollments:write
Keep one token per brand and reuse it for its hour. A call with brand A's token never sees brand B's patients; it answers 404 as if they did not exist.
| Problem | code |
HTTP |
|---|---|---|
No tenant_id |
tenant_required |
400 |
| A brand you may not act for, or one that is not live | tenant_not_permitted |
400 |
tenant_not_permitted is the same answer for an unknown id and for another partner's brand. Rate limits count per brand: each brand has its own limit under your client, so one busy brand does not slow the others.
Sign every brand's assertions with the same key and issuer, and add a tenant_id claim naming the brand the patient is enrolled with:
{
"iss": "https://partner.example.com",
"sub": "brand-pt-48213",
"aud": "https://sandbox.rx.tera.health",
"tenant_id": "a0000000-0000-4000-8000-000000000002",
"iat": 1793635200,
"exp": 1793635500,
"jti": "5f0c9a52-8a4e-4d4f-9a52-0f6e8b7c1d2e"
}
sub is the external_patient_id you sent on POST /v1/enrollments for that brand. Your ids may repeat across brands; TeraRx looks sub up within the brand named by tenant_id. An assertion without tenant_id is refused with assertion_invalid, one naming a brand you may not act for with tenant_not_permitted. Everything else is as in Authentication.
GET /v1/tenants (scope tenants:read) lists every brand under your partner account, whichever brand the token is for, with its id, slug, display_name and is_enabled (whether it is live), ordered by slug and paginated with limit and cursor. Use it to check your configuration against ours. You need one brand's tenant_id, from onboarding, to get the first token.
Events for all your brands go to your partner endpoint, signed with your secret, unless a brand has its own endpoint. Every event carries tenant_id: route on it. The payloads and signature are as in Webhooks.
GET /v1/reports/brand reports on the brand the token is for; ask once per brand. Under your agreement, TeraRx sends one monthly invoice for all your brands, with a section per brand.
A platform partner's client sees every brand under the partner, whichever brand its token is for. A brand's own
client sees its own brand. Use the ids as tenant_id on the token request and in patient assertions. A cursor
this list did not return gets 422 validation_failed, so a partial list is never mistaken for the whole.
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Tenant) Ordered by |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "a0000000-0000-4000-8000-000000000002",
- "slug": "northwind-health",
- "display_name": "Northwind Health",
- "is_enabled": true
}, - {
- "id": "a0000000-0000-4000-8000-000000000003",
- "slug": "summit-wellness",
- "display_name": "Summit Wellness",
- "is_enabled": false
}
], - "next_cursor": null
}A patient's enrollment in coaching with one brand. Brand servers create and manage them and read status only, never clinical content.
Call this at checkout, or when an existing patient subscribes. In one transaction TeraRx creates the patient
record for your brand (or reuses it for a returning patient), the enrollment, the consent records, the first
treatment version and the onboarding form assignments from TeraRx's form rules for your plan, prefilled from intake.
On the coached plan it then queues coach assignment for each care role.
Fires enrollment.created, then form.requested per form and, once coaches are assigned, care_team.assigned.
Unique by external_order_id, and by external_patient_id among active and paused enrollments.
Error codes: enrollment_exists, external_order_exists (409); plan_not_offered, unknown_medication,
unknown_legal_document, validation_failed (422).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| plan_code | string (PlanCode) Enum: "ai_only" "ai_health_fitness" Omit to use the brand's default plan. |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your patient id. It is the |
| external_order_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your order id. Unique per brand, so a retried checkout cannot enroll twice. |
| purchased_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
required | object (PatientProfileInput) What the brand already knows about the patient. Stored on the patient record for this brand only. |
object Intake answers you already collected. Keys that match a form field's | |
Array of objects (ConsentProofInput) <= 20 items | |
object (TreatmentInput) The medication, dose and schedule your prescriber set. TeraRx mirrors it and never changes it. Each call adds a version; the latest is the current treatment and earlier ones are the dose history. | |
| data_sharing | boolean Default: true Whether the care team may read this patient's logs. The patient can change it later. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| Location | string Example: "/v1/enrollments/0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5" URL of the new enrollment. |
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "plan_code": "ai_health_fitness",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "purchased_at": "2026-10-21T14:58:00Z",
- "patient": {
- "first_name": "Jordan",
- "last_name": "Rivera",
- "email": "jordan.rivera@example.com",
- "phone": "+12025550143",
- "date_of_birth": "1986-04-12",
- "sex": "female",
- "height_cm": 165.1,
- "state_code": "NY",
- "timezone": "America/New_York",
- "units": "imperial",
- "conditions": [
- "prediabetes"
], - "health_profile": {
- "goal_weight_kg": 72.5,
- "activity_level": "lightly_active",
- "food_allergies": [
- "peanuts"
], - "dietary_restrictions": [
- "dairy_free"
], - "food_dislikes": [
- "mushrooms"
], - "cooking": {
- "skill": "beginner",
- "appliances": [
- "stove_range",
- "microwave",
- "air_fryer"
], - "meal_preparer": "mostly_myself"
}, - "lifestyle": {
- "sleep_hours": 6.5,
- "alcohol": "occasional",
- "smoking": "never"
}
}
}, - "intake": {
- "weight_kg": 96.2,
- "goal_weight_kg": 72.5,
- "activity_level": "lightly_active"
}, - "consents": [
- {
- "document_code": "coaching_consent",
- "version": "2026-01",
- "accepted_at": "2026-10-21T14:57:30Z",
- "evidence": {
- "ip": "203.0.113.24",
- "checkout_id": "chk_2026_10_000123"
}
}
], - "treatment": {
- "medication_id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "dose_mg": 0.25,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28"
}
}{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "awaiting_assignment",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": null,
- "onboarding_completed_at": null,
- "first_protocol_published_at": null,
- "last_activity_at": null,
- "ended_at": null,
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": false,
- "assigned_at": null
}, - {
- "care_role": "fitness_coach",
- "assigned": false,
- "assigned_at": null
}
], - "forms": [
- {
- "form_assignment_id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form_code": "glp1_onboarding",
- "purpose": "onboarding",
- "status": "sent",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "completed_at": null
}
], - "protocols": [ ]
}Your brand's enrollments, newest first. Filter by external_patient_id to find one patient's enrollments.
| external_patient_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: external_patient_id=brand-pt-48213 Your own identifier, printable ASCII without spaces. |
| status | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" Example: status=active |
| risk_level | string (RiskLevel) Enum: "normal" "elevated" "high" Example: risk_level=high Raised by clinical flags. Only a prescriber's resolve lowers it. |
| plan_code | string (PlanCode) Enum: "ai_only" "ai_health_fitness" Example: plan_code=ai_only
|
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Enrollment) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": null,
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:10:00Z"
}, - {
- "care_role": "fitness_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:12:00Z"
}
], - "forms": [
- {
- "form_assignment_id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form_code": "glp1_onboarding",
- "purpose": "onboarding",
- "status": "completed",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "completed_at": "2026-10-22T09:30:00Z"
}
], - "protocols": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 1,
- "published_at": "2026-10-23T16:45:00Z"
}
]
}
], - "next_cursor": null
}The enrollment's stage, care team status, form status, protocol timestamps and risk level. It carries no clinical content.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_only",
- "status": "active",
- "stage": "protocols_active",
- "risk_level": "elevated",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": null,
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-22T09:31:10Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": null,
- "care_team": [ ],
- "forms": [
- {
- "form_assignment_id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form_code": "glp1_onboarding",
- "purpose": "onboarding",
- "status": "completed",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "completed_at": "2026-10-22T09:30:00Z"
}
], - "protocols": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 2,
- "published_at": "2026-10-25T08:00:00Z"
}
]
}Contact details, demographics, time zone, conditions and the health profile, when they change in your system. Changes reach the care team at once and apply to plans and AI Coach replies generated after them.
A new state_code matters: coaches are licensed per state. A coach not licensed in the new state is taken off the
patient and a licensed one is found, so you get care_team.changed with reason_code: state_change (or
care_team.capacity_warning if none is free). Until then that role shows assigned: false in the response.
Error codes: enrollment_not_active (409) for a cancelled or completed enrollment, validation_failed (422).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| first_name | string (first_name) <= 100 characters |
| last_name | string (last_name) <= 100 characters |
string <email> (email) <= 254 characters | |
| phone | string (phone) ^\+[1-9][0-9]{7,14}$ Phone number in E.164 format. |
| date_of_birth | string <date> (date_of_birth) |
| sex | string (BiologicalSex) Enum: "female" "male" "intersex" |
| height_cm | number (height_cm) [ 50 .. 260 ] |
| state_code | string (StateCode) ^[A-Z]{2}$ US state of residence (USPS code). Decides which licensed coaches can be assigned. |
| timezone | string (timezone) IANA time zone. Log dates and reminders follow it. |
| language | string (language) BCP 47 language tag. |
| units | string (UnitSystem) Enum: "metric" "imperial" Display preference only. The API always takes and returns metric values. |
| conditions | Array of strings (conditions) [ items^[a-z][a-z0-9_]*$ ] Condition codes TeraRx's clinical rules use, for example |
object (HealthProfile) What plans and the AI Coach are built around. Every field is optional. In a |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "phone": "+12025550178",
- "state_code": "NJ",
- "health_profile": {
- "food_allergies": [
- "peanuts",
- "sesame"
]
}
}{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": null,
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": false,
- "assigned_at": null
}, - {
- "care_role": "fitness_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:12:00Z"
}
], - "forms": [ ],
- "protocols": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 1,
- "published_at": "2026-10-23T16:45:00Z"
}
]
}Moves the enrollment between ai_health_fitness and ai_only. Moving to ai_only ends the care team assignments
(care_team.changed); moving to the coached plan queues assignment (care_team.assigned follows).
Billing switches plan from the change. Error codes: plan_not_offered (422), same_plan,
invalid_status_transition (409).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| reason | string <= 500 characters Kept on the enrollment history. Do not include health details. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "plan_code": "ai_only",
- "reason": "Patient downgraded at renewal"
}{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_only",
- "status": "active",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": null,
- "care_team": [ ],
- "forms": [ ],
- "protocols": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 1,
- "published_at": "2026-10-23T16:45:00Z"
}
]
}Paused days are not billed. The patient keeps read access to their plan; reminders and new protocols stop.
The enrollment stays paused until you call resume.
Fires enrollment.paused. Error code: invalid_status_transition (409) unless the enrollment is active.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| reason | string <= 500 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "reason": "Patient travelling"
}{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "paused",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": null,
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:10:00Z"
}, - {
- "care_role": "fitness_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:12:00Z"
}
], - "forms": [ ],
- "protocols": [ ]
}Returns a paused enrollment to active. Fires enrollment.resumed. Error code: invalid_status_transition (409) unless the enrollment is paused.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-11-15T08:02:00Z",
- "ended_at": null,
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:10:00Z"
}, - {
- "care_role": "fitness_coach",
- "assigned": true,
- "assigned_at": "2026-10-21T15:12:00Z"
}
], - "forms": [ ],
- "protocols": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 1,
- "published_at": "2026-10-23T16:45:00Z"
}
]
}Ends the enrollment and the care team assignments, and starts the retention clock for the patient's records
(your contract's retention period). Fires enrollment.cancelled. Error code: invalid_status_transition (409)
when the enrollment has already ended.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| reason required | string Enum: "patient_request" "subscription_ended" "payment_failed" "medically_ineligible" "other" |
| note | string <= 500 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| external_order_id | string or null |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| status required | string (EnrollmentStatus) Enum: "pending_screening" "ineligible" "active" "paused" "cancelled" "completed" |
| stage required | string (EnrollmentStage) Enum: "created" "awaiting_assignment" "assigned" "onboarding_pending" "onboarding_completed" "protocols_active" |
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" Raised by clinical flags. Only a prescriber's resolve lowers it. |
| data_sharing required | boolean Whether the care team may read this patient's logs. |
| purchased_at | string or null <date-time> |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| assigned_at | string or null <date-time> When the last care role was filled. |
| onboarding_completed_at | string or null <date-time> |
| first_protocol_published_at | string or null <date-time> |
| last_activity_at | string or null <date-time> Last patient activity, updated about hourly. |
| ended_at | string or null <date-time> |
required | Array of objects (CareTeamStatus) Empty on |
required | Array of objects (FormStatus) |
required | Array of objects (ProtocolStatusSummary) Active protocols. |
{- "reason": "subscription_ended"
}{- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "cancelled",
- "stage": "protocols_active",
- "risk_level": "normal",
- "data_sharing": true,
- "purchased_at": "2026-10-21T14:58:00Z",
- "created_at": "2026-10-21T14:58:02Z",
- "assigned_at": "2026-10-21T15:12:00Z",
- "onboarding_completed_at": "2026-10-22T09:30:00Z",
- "first_protocol_published_at": "2026-10-23T16:45:00Z",
- "last_activity_at": "2026-10-26T13:40:00Z",
- "ended_at": "2026-12-21T00:00:00Z",
- "care_team": [
- {
- "care_role": "health_coach",
- "assigned": false,
- "assigned_at": null
}, - {
- "care_role": "fitness_coach",
- "assigned": false,
- "assigned_at": null
}
], - "forms": [ ],
- "protocols": [ ]
}The AI Coach starts a short check-in chat with the patient, for example after patient.disengaged. It goes out
within minutes, outside the patient's quiet hours; you get message.created when it does, and send your push
then. At most one request every 3 days per enrollment. Error codes: outreach_too_soon (409),
outreach_off (409) when the patient turned AI Coach outreach off, ai_paused (409),
enrollment_not_active (409).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
{- "id": "0190f5e4-5d6e-7f80-9a1b-c2d3e4f5a6b7",
- "status": "scheduled",
- "scheduled_for": "2026-10-27T15:00:00Z",
- "sent_at": null
}The GLP-1 medication, dose and schedule the brand's prescriber set, mirrored by TeraRx and never changed.
The GLP-1 products you can name in a treatment. It includes the platform catalog plus any added for your brand.
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Medication) |
{- "data": [
- {
- "id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection",
- "standard_doses_mg": [
- 0.25,
- 0.5,
- 1,
- 1.7,
- 2.4
], - "scope": "platform"
}, - {
- "id": "9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
- "ingredient": "tirzepatide",
- "product_name": "Compounded tirzepatide",
- "source": "compounded",
- "route": "injection",
- "standard_doses_mg": [ ],
- "scope": "platform"
}
]
}Send the treatment whenever your prescriber sets or changes it: a new dose, a phase change, a pause or a stop. Each call appends a version and the latest becomes current. TeraRx never changes a dose; protocols are rewritten for the new dose.
Error codes: unknown_medication, validation_failed (422); invalid_status_transition (409) on an ended enrollment.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| medication_id required | string <uuid> (Uuid) From |
| dose_mg required | number ( 0 .. 100 ] |
| dose_amount | number The dose as prescribed, when it is not in mg (for example 20 units of a compounded vial). |
| dose_unit | string <= 20 characters The unit of |
| phase required | string (TreatmentPhase) Enum: "pending_start" "titration" "maintenance" "taper" "off_ramp" "paused" "stopped" |
object A custom titration ladder or microdosing schedule. | |
| injection_weekday | integer [ 0 .. 6 ] 0 is Sunday. Drives injection-day reminders. |
| started_on | string <date> First dose. The treatment week is counted from it. |
| effective_from | string <date-time> (Timestamp) When this dose takes effect. Defaults to now. |
| note | string <= 1000 characters Visible to the care team only. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
required | object (Medication) |
| dose_mg required | number |
| dose_amount | number or null The dose as prescribed, when it is not in mg (for example 20 units of a compounded vial). |
| dose_unit | string or null The unit of |
| phase required | string (TreatmentPhase) Enum: "pending_start" "titration" "maintenance" "taper" "off_ramp" "paused" "stopped" |
object or null A custom titration ladder or microdosing schedule. | |
| injection_weekday | integer or null |
| started_on | string or null <date> |
| treatment_week | integer or null Week of treatment today, counted from |
| effective_from required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| note | string or null |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "medication_id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "dose_mg": 0.5,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "effective_from": "2026-10-26T00:00:00Z",
- "note": "Step up to 0.5 mg per titration schedule"
}{- "id": "0190f5c7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
- "medication": {
- "id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection",
- "standard_doses_mg": [
- 0.25,
- 0.5,
- 1,
- 1.7,
- 2.4
], - "scope": "platform"
}, - "dose_mg": 0.5,
- "dose_amount": null,
- "dose_unit": null,
- "phase": "titration",
- "schedule": null,
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "treatment_week": 5,
- "effective_from": "2026-10-26T00:00:00Z",
- "source": "brand_api",
- "note": "Step up to 0.5 mg per titration schedule",
- "created_at": "2026-10-26T14:05:00Z"
}The mirrored treatment history, newest first. Use it to reconcile with your own records.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Treatment) Newest first. The first item is the current treatment. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
- "medication": {
- "id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection",
- "standard_doses_mg": [
- 0.25,
- 0.5,
- 1,
- 1.7,
- 2.4
], - "scope": "platform"
}, - "dose_mg": 0.5,
- "dose_amount": null,
- "dose_unit": null,
- "phase": "titration",
- "schedule": null,
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "treatment_week": 5,
- "effective_from": "2026-10-26T00:00:00Z",
- "source": "brand_api",
- "note": "Step up to 0.5 mg per titration schedule",
- "created_at": "2026-10-26T14:05:00Z"
}
], - "next_cursor": null
}Proof that the patient accepted legal documents in your checkout, when it was not sent with the enrollment.
Documents still not accepted are shown to the patient on first open (GET /v1/patient/legal-documents).
Recording a document the patient already accepted keeps the first acceptance.
Error code: unknown_legal_document (422).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
required | Array of objects (ConsentProofInput) [ 1 .. 20 ] items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
required | Array of objects (ConsentRecord) |
{- "consents": [
- {
- "document_code": "terms",
- "version": "2026-01",
- "accepted_at": "2026-10-21T14:57:30Z",
- "evidence": {
- "ip": "203.0.113.24",
- "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X)",
- "checkout_id": "chk_2026_10_000123"
}
}
]
}{- "data": [
- {
- "id": "0190f5c8-2b3c-7d4e-8f50-6a7b8c9d0e1f",
- "document_code": "terms",
- "document_version": "2026-01",
- "accepted_at": "2026-10-21T14:57:30Z",
- "withdrawn_at": null,
- "source": "brand_api"
}
]
}Every document the patient has accepted for this enrollment, from your checkout or from the app.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (ConsentRecord) |
{- "data": [
- {
- "id": "0190f5c8-2b3c-7d4e-8f50-6a7b8c9d0e1f",
- "document_code": "coaching_consent",
- "document_version": "2026-01",
- "accepted_at": "2026-10-21T14:57:30Z",
- "withdrawn_at": null,
- "source": "brand_api"
}, - {
- "id": "0190f5c8-3c4d-7e5f-9a61-7b8c9d0e1f2a",
- "document_code": "privacy",
- "document_version": "2026-01",
- "accepted_at": "2026-10-22T09:05:12Z",
- "withdrawn_at": null,
- "source": "brand_app"
}
]
}Logs the brand already holds, sent with the brand's own ids for safe retries, and corrected or deleted by those ids. Brand servers get receipts, never the values back.
A meal your app already recorded. Returns a receipt, not the values. A repeated external_id returns the
first receipt with 200. While the patient has data sharing off, the log is stored but the care team does not see it.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| eaten_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (Uuid) Set when the patient ate a meal from their plan. |
| description | string <= 1000 characters |
| photo_upload_id | string <uuid> (Uuid) From |
| entry_method | string Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number [ 0 .. 500 ] |
| calories_kcal | integer [ 0 .. 10000 ] |
| carbs_g | number >= 0 |
| fat_g | number >= 0 |
| fiber_g | number >= 0 |
| sugar_g | number >= 0 |
| sodium_mg | integer >= 0 |
object Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (Ingredient) <= 50 items | |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your id for this log. A second log with the same id returns the first one instead of a duplicate. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "eaten_at": "2026-10-26T12:30:00Z",
- "meal_type": "lunch",
- "description": "Chicken salad",
- "entry_method": "manual",
- "protein_g": 35,
- "calories_kcal": 420,
- "external_id": "meal-77120"
}{- "id": "0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60",
- "external_id": "meal-77120",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T12:31:04Z",
- "updated_at": "2026-10-26T12:31:04Z"
}A workout your app already recorded. A repeated external_id returns the first receipt with 200.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| started_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type required | string <= 50 characters For example |
| duration_min | integer [ 1 .. 600 ] |
| completed | boolean Default: true |
| perceived_effort | integer [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (Uuid) |
Array of objects (WorkoutSet) <= 100 items | |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "started_at": "2026-10-26T07:00:00Z",
- "activity_type": "walk",
- "duration_min": 30,
- "external_id": "workout-5531"
}{- "id": "0190f5c9-6e7f-7081-9ba3-2c3d4e5f6071",
- "external_id": "workout-5531",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T07:40:00Z",
- "updated_at": "2026-10-26T07:40:00Z"
}A symptom your app or clinicians already recorded. Alert rules run on it exactly as on a patient's own log,
so a red flag here raises a clinical flag. A repeated external_id returns the first receipt with 200.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| occurred_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom required | string^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity required | integer [ 1 .. 5 ] |
| note | string <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "occurred_at": "2026-10-26T19:45:00Z",
- "symptom": "constipation",
- "severity": 3,
- "external_id": "sym-20931"
}{- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "external_id": "sym-20931",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T19:46:10Z",
- "updated_at": "2026-10-26T19:46:10Z"
}A dose taken or missed, as your app recorded it. A repeated external_id returns the first receipt with 200.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| injected_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed | boolean Default: false |
| scheduled_for | string <date> |
| dose_mg | number > 0 |
| site | string For example |
| note | string <= 1000 characters |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "injected_at": "2026-10-26T08:00:00Z",
- "scheduled_for": "2026-10-26",
- "dose_mg": 0.5,
- "site": "thigh_right",
- "external_id": "inj-88213"
}{- "id": "0190f5c9-8091-72a3-9dc5-4e5f60718293",
- "external_id": "inj-88213",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T08:01:30Z",
- "updated_at": "2026-10-26T08:01:30Z"
}Corrects a meal your server sent, found by your external_id. A log the patient made in the app can't be
changed here (403 log_not_owned). Returns the receipt with the new updated_at; the values stay with the care
team.
Error codes: log_not_owned (403), upload_not_found, meal_plan_item_not_found (422).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
| eaten_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (meal_plan_item_id) Set when the patient ate a meal from their plan. |
| description | string (description) <= 1000 characters |
| photo_upload_id | string <uuid> (photo_upload_id) From |
| entry_method | string (entry_method) Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number (protein_g) [ 0 .. 500 ] |
| calories_kcal | integer (calories_kcal) [ 0 .. 10000 ] |
| carbs_g | number (carbs_g) >= 0 |
| fat_g | number (fat_g) >= 0 |
| fiber_g | number (fiber_g) >= 0 |
| sugar_g | number (sugar_g) >= 0 |
| sodium_mg | integer (sodium_mg) >= 0 |
object (nutrients) Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (ingredients) <= 50 items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "protein_g": 30,
- "calories_kcal": 360
}{- "id": "0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60",
- "external_id": "meal-77120",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T12:31:04Z",
- "updated_at": "2026-10-26T13:02:00Z"
}Deletes a meal your server sent, for example one recorded against the wrong patient. Deleting it again returns
204. Its external_id stays taken: sending it again returns the deleted log's receipt, so use a new id for a
replacement.
Error code: log_not_owned (403).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Corrects a workout your server sent, found by your external_id. A log the patient made in the app can't be
changed here (403 log_not_owned). Returns the receipt with the new updated_at; the values stay with the care
team.
Error codes: log_not_owned (403), workout_plan_item_not_found (422).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
| started_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type | string (activity_type) <= 50 characters For example |
| duration_min | integer (duration_min) [ 1 .. 600 ] |
| completed | boolean (completed) |
| perceived_effort | integer (perceived_effort) [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (workout_plan_item_id) |
Array of objects (sets) <= 100 items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "duration_min": 45
}{- "id": "0190f5c9-6e7f-7081-9ba3-2c3d4e5f6071",
- "external_id": "workout-5531",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T07:40:00Z",
- "updated_at": "2026-10-26T08:10:00Z"
}Deletes a workout your server sent, for example one recorded against the wrong patient. Deleting it again returns
204. Its external_id stays taken: sending it again returns the deleted log's receipt, so use a new id for a
replacement.
Error code: log_not_owned (403).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Corrects a symptom your server sent, found by your external_id. A log the patient made in the app can't be
changed here (403 log_not_owned). Returns the receipt with the new updated_at; the values stay with the care
team.
Editing or deleting a symptom never closes a clinical flag or lowers the risk level; only the prescriber's resolve
does. A correction that makes a symptom more severe runs the alert rules again, like a new log, so it can raise a
flag. A flag raised by a symptom deleted later shows deleted_at on its triggering record.
Error code: log_not_owned (403).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
| occurred_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom | string (symptom) ^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity | integer (severity) [ 1 .. 5 ] |
| note | string (note) <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "severity": 3,
- "note": "Worse after dinner than I first logged"
}{- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "external_id": "sym-20931",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T19:46:10Z",
- "updated_at": "2026-10-26T20:30:00Z"
}Deletes a symptom your server sent, for example one recorded against the wrong patient. Deleting it again returns
204. Its external_id stays taken: sending it again returns the deleted log's receipt, so use a new id for a
replacement.
Editing or deleting a symptom never closes a clinical flag or lowers the risk level; only the prescriber's resolve
does. A correction that makes a symptom more severe runs the alert rules again, like a new log, so it can raise a
flag. A flag raised by a symptom deleted later shows deleted_at on its triggering record.
Error code: log_not_owned (403).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Corrects an injection your server sent, found by your external_id. A log the patient made in the app can't be
changed here (403 log_not_owned). Returns the receipt with the new updated_at; the values stay with the care
team.
Changing a missed dose to taken (or back) does not close an alert already raised for it.
Error codes: log_not_owned (403), injection_incomplete (422) when the result is neither taken nor missed.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
| injected_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed | boolean (missed) |
| scheduled_for | string <date> (scheduled_for) |
| dose_mg | number (dose_mg) > 0 |
| site | string (site) For example |
| note | string (note-2) <= 1000 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "missed": false,
- "injected_at": "2026-10-26T08:00:00Z",
- "dose_mg": 0.5
}{- "id": "0190f5c9-8091-72a3-9dc5-4e5f60718293",
- "external_id": "inj-88213",
- "log_date": "2026-10-26",
- "created_at": "2026-10-26T08:01:30Z",
- "updated_at": "2026-10-26T09:15:00Z"
}Deletes an injection your server sent, for example one recorded against the wrong patient. Deleting it again returns
204. Its external_id stays taken: sending it again returns the deleted log's receipt, so use a new id for a
replacement.
Changing a missed dose to taken (or back) does not close an alert already raised for it.
Error code: log_not_owned (403).
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: meal-77120 Your |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Weight, body measures, water, steps, sleep and other daily values your app recorded. Only the fields sent change.
| enrollment_id required | string <uuid> Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| log_date required | string <date> Example: 2026-10-26 The day, in the patient's time zone. |
| water_ml | integer or null [ 0 .. 20000 ] |
| weight_kg | number or null [ 20 .. 400 ] |
| waist_cm | number or null [ 30 .. 300 ] |
| hip_cm | number or null [ 30 .. 300 ] |
| body_fat_pct | number or null [ 1 .. 80 ] |
| steps | integer or null [ 0 .. 200000 ] |
| sleep_minutes | integer or null [ 0 .. 1440 ] |
| sleep_quality | integer or null (Score) [ 1 .. 5 ] |
| mood | integer or null (Score) [ 1 .. 5 ] |
| stress | integer or null (Score) [ 1 .. 5 ] |
| energy | integer or null (Score) [ 1 .. 5 ] |
object Habit check-offs keyed | |
object Any other daily metric, keyed by a metric code. New codes need no API change. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| updated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "weight_kg": 94.8,
- "steps": 7400
}{- "log_date": "2026-10-26",
- "updated_at": "2026-10-26T14:05:00Z"
}The same events as the webhooks, oldest first, for the last 30 days. After an outage, pass the id of the last
event you processed as since, then follow next_cursor until it is null. Every event appears exactly once,
in order; events outside the 30-day window are gone.
Error code: event_too_old (410) when since is older than 30 days.
| since | string <uuid> Example: since=0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 Start after this event id (exclusive). Omit to start at the oldest event kept. |
| type | Array of strings (EventType) Items Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Example: type=clinical.flag_raised,clinical.flag_redelivered Only these event types, comma-separated. |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of any (Event) Oldest first, in the order they were written. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7",
- "type": "clinical.flag_raised",
- "created_at": "2026-10-26T14:05:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "severity": "critical",
- "raised_risk_to": "high",
- "ack_due_at": "2026-10-26T16:05:00Z"
}
}, - {
- "id": "0190f5e1-0a1b-7c2d-9e3f-4a5b6c7d8e9f",
- "type": "clinical.flag_acknowledged",
- "created_at": "2026-10-26T14:20:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "acknowledged_at": "2026-10-26T14:20:00Z"
}
}
], - "next_cursor": null
}One event by id, within the 30-day window.
| event_id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Value: "enrollment.created" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (EnrollmentCreatedData) |
{- "id": "0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7",
- "type": "clinical.flag_raised",
- "created_at": "2026-10-26T14:05:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "severity": "critical",
- "raised_risk_to": "high",
- "ack_due_at": "2026-10-26T16:05:00Z"
}
}Enrollment counts, engagement and outcomes for a period, in total and per plan. Aggregates only, never
patient-level values; any metric whose group has fewer than 10 patients is null. Ask for
Accept: application/pdf for the same report as a PDF. Periods can be up to 366 days.
Error code: period_too_long (422).
| from required | string <date> Example: from=2026-10-01 |
| to required | string <date> Example: to=2026-10-31 |
| plan_code | string (PlanCode) Enum: "ai_only" "ai_health_fitness" Example: plan_code=ai_only Only this plan. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object |
| generated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| min_group_size required | integer Groups smaller than this are suppressed. Value: 10 |
required | object (ReportMetrics) Aggregates only, no patient-level values. Each metric is suppressed on its own when its group is under 10 patients. |
required | Array of objects |
{- "period": {
- "from": "2026-10-01",
- "to": "2026-10-31"
}, - "generated_at": "2026-11-01T06:00:00Z",
- "min_group_size": 10,
- "totals": {
- "active_enrollments": 214,
- "new_enrollments": 61,
- "cancelled_enrollments": 12,
- "engaged_last_7_days": 168,
- "median_weight_change_kg": -4.6,
- "protein_target_days_pct": 0.57,
- "flags_raised": 18,
- "flags_acknowledged_on_time_pct": 0.94,
- "ai_coach_thumbs_up_pct": 0.88,
- "retention_90_day_pct": null,
- "median_hours_to_first_protocol": 26.5
}, - "plans": [
- {
- "plan_code": "ai_health_fitness",
- "metrics": {
- "active_enrollments": 131,
- "new_enrollments": 35,
- "cancelled_enrollments": null,
- "engaged_last_7_days": 109,
- "median_weight_change_kg": -5.1,
- "protein_target_days_pct": 0.63,
- "flags_raised": 11,
- "flags_acknowledged_on_time_pct": 1,
- "ai_coach_thumbs_up_pct": 0.9,
- "retention_90_day_pct": null,
- "median_hours_to_first_protocol": 31
}
}, - {
- "plan_code": "ai_only",
- "metrics": {
- "active_enrollments": 83,
- "new_enrollments": 26,
- "cancelled_enrollments": null,
- "engaged_last_7_days": 59,
- "median_weight_change_kg": -3.8,
- "protein_target_days_pct": 0.48,
- "flags_raised": null,
- "flags_acknowledged_on_time_pct": null,
- "ai_coach_thumbs_up_pct": 0.86,
- "retention_90_day_pct": null,
- "median_hours_to_first_protocol": 0.1
}
}
]
}Export, delete and amend requests a patient makes through the brand. Moving a patient's records to another brand is not an API request: send TeraRx support the patient's signed authorization and support handles the transfer.
A patient's request, made through you, to export, delete or amend their coaching records. Requests are
processed in the background; data_request.completed fires when done.
Error codes: data_request_in_progress (409) for a second open request of the same kind, validation_failed (422).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| enrollment_id required | string <uuid> (Uuid) |
| kind required | string |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| kind required | string (DataRequestKind) Enum: "export" "delete" "amend"
|
| status required | string (DataRequestStatus) Enum: "received" "processing" "completed" "rejected" |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| completed_at | string or null <date-time> |
object or null Export requests, once completed. | |
object or null Amend requests only. |
{- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "kind": "export"
}{- "id": "7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "kind": "export",
- "status": "received",
- "requested_at": "2026-10-26T14:05:00Z",
- "completed_at": null,
- "export": null,
- "amendment": null
}Your brand's data requests, newest first.
| status | string (DataRequestStatus) Enum: "received" "processing" "completed" "rejected" Example: status=processing |
| kind | string (DataRequestKind) Enum: "export" "delete" "amend" Example: kind=amend
|
| enrollment_id | string <uuid> Example: enrollment_id=0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (DataRequest) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "kind": "amend",
- "status": "processing",
- "requested_at": "2026-10-26T14:05:00Z",
- "completed_at": null,
- "export": null,
- "amendment": {
- "details": {
- "record_type": "symptom_log",
- "record_id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "description": "The severity was logged as 5 by mistake.",
- "requested_change": "Severity should be 2."
}, - "respond_by": "2026-12-25T14:05:00Z",
- "decision": null,
- "decision_note": null,
- "decided_at": null
}
}
], - "next_cursor": null
}The request's status and, for exports, whether the download is ready.
| data_request_id required | string <uuid> Example: 7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| kind required | string (DataRequestKind) Enum: "export" "delete" "amend"
|
| status required | string (DataRequestStatus) Enum: "received" "processing" "completed" "rejected" |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| completed_at | string or null <date-time> |
object or null Export requests, once completed. | |
object or null Amend requests only. |
{- "id": "7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "kind": "export",
- "status": "completed",
- "requested_at": "2026-10-26T14:05:00Z",
- "completed_at": "2026-10-26T14:40:00Z",
- "export": {
- "available": true,
- "expires_at": "2026-11-02T14:40:00Z"
}, - "amendment": null
}A short-lived link to the export file. Ask for a new link each time you download; the file itself stays until
export.expires_at. Error codes: export_not_ready (409), export_expired (410).
| data_request_id required | string <uuid> Example: 7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| url required | string <uri> Pre-signed download link. Expires within minutes; ask for a new one if needed. |
| expires_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "expires_at": "2026-10-26T14:50:00Z"
}Accept or deny an amend request within 60 days of requested_at. A denial needs decision_note (HIPAA requires
the reason). Accepting applies the change and records the previous value in the record history.
Error codes: not_an_amend_request, already_decided (409).
| data_request_id required | string <uuid> Example: 7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| decision required | string Value: "accepted" |
| decision_note | string <= 2000 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| kind required | string (DataRequestKind) Enum: "export" "delete" "amend"
|
| status required | string (DataRequestStatus) Enum: "received" "processing" "completed" "rejected" |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| completed_at | string or null <date-time> |
object or null Export requests, once completed. | |
object or null Amend requests only. |
{- "decision": "accepted",
- "decision_note": "Corrected the severity to 2."
}{- "id": "7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "kind": "amend",
- "status": "completed",
- "requested_at": "2026-10-26T14:05:00Z",
- "completed_at": "2026-10-28T10:00:00Z",
- "export": null,
- "amendment": {
- "details": {
- "record_type": "symptom_log",
- "record_id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "description": "The severity was logged as 5 by mistake.",
- "requested_change": "Severity should be 2."
}, - "respond_by": "2026-12-25T14:05:00Z",
- "decision": "accepted",
- "decision_note": "Corrected the severity to 2.",
- "decided_at": "2026-10-28T10:00:00Z"
}
}The patient's profile, plan, care team and brand details. Call it first after the token exchange.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object |
required | object |
required | Array of objects (CareTeamMember) Empty on |
required | object (BrandInfo) |
| legal_documents_pending | integer Required legal documents not accepted yet. When above 0, show |
{- "patient": {
- "first_name": "Jordan",
- "last_name": "Rivera",
- "email": "jordan.rivera@example.com",
- "phone": "+12025550143",
- "date_of_birth": "1986-04-12",
- "sex": "female",
- "height_cm": 165.1,
- "state_code": "NY",
- "timezone": "America/New_York",
- "language": "en",
- "units": "imperial",
- "conditions": [
- "prediabetes"
], - "health_profile": {
- "goal_weight_kg": 72.5,
- "activity_level": "lightly_active",
- "food_allergies": [
- "peanuts"
], - "dietary_restrictions": [
- "dairy_free"
], - "food_dislikes": [
- "mushrooms"
], - "cooking": {
- "skill": "beginner",
- "appliances": [
- "stove_range",
- "microwave",
- "air_fryer"
], - "meal_preparer": "mostly_myself"
}, - "lifestyle": {
- "sleep_hours": 6.5,
- "alcohol": "occasional",
- "smoking": "never"
}
}
}, - "enrollment": {
- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "protocols_active",
- "data_sharing": true
}, - "care_team": [
- {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD",
- "NBC-HWC"
], - "assigned_at": "2026-10-21T15:10:00Z"
}
], - "brand": {
- "display_name": "Demo Brand",
- "primary_color": "#1A5F7A",
- "support": {
- "email": "support@example.com",
- "phone": "+12025550100",
}
}, - "legal_documents_pending": 0
}Height; the health profile (activity level, food allergies, dietary restrictions, food dislikes, cooking and
lifestyle); time zone, language, display units and data sharing. Changes apply to plans and AI Coach replies
generated after them. Turning data_sharing off is recorded on the enrollment history and takes effect
immediately for the care team.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| height_cm | number [ 50 .. 260 ] The patient's height. Metric, like every measurement; convert from |
| timezone | string |
| language | string |
| units | string (UnitSystem) Enum: "metric" "imperial" Display preference only. The API always takes and returns metric values. |
| data_sharing | boolean Turning it off hides the patient's logs, protocols and AI tools from the care team. |
object (HealthProfile) What plans and the AI Coach are built around. Every field is optional. In a |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object |
required | object |
required | Array of objects (CareTeamMember) Empty on |
required | object (BrandInfo) |
| legal_documents_pending | integer Required legal documents not accepted yet. When above 0, show |
{- "height_cm": 167.6,
- "units": "metric",
- "health_profile": {
- "dietary_restrictions": [
- "dairy_free",
- "gluten_free"
], - "cooking": {
- "skill": "intermediate"
}
}
}{- "patient": {
- "first_name": "Jordan",
- "last_name": "Rivera",
- "email": "jordan.rivera@example.com",
- "phone": "+12025550143",
- "date_of_birth": "1986-04-12",
- "sex": "female",
- "height_cm": 167.6,
- "state_code": "NY",
- "timezone": "America/New_York",
- "language": "en",
- "units": "metric",
- "conditions": [
- "prediabetes"
], - "health_profile": {
- "goal_weight_kg": 72.5,
- "activity_level": "lightly_active",
- "food_allergies": [
- "peanuts"
], - "dietary_restrictions": [
- "dairy_free",
- "gluten_free"
], - "food_dislikes": [
- "mushrooms"
], - "cooking": {
- "skill": "intermediate",
- "appliances": [
- "stove_range",
- "microwave",
- "air_fryer"
], - "meal_preparer": "mostly_myself"
}, - "lifestyle": {
- "sleep_hours": 6.5,
- "alcohol": "occasional",
- "smoking": "never"
}
}
}, - "enrollment": {
- "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "protocols_active",
- "data_sharing": true
}, - "care_team": [
- {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD",
- "NBC-HWC"
], - "assigned_at": "2026-10-21T15:10:00Z"
}
], - "brand": {
- "display_name": "Demo Brand",
- "primary_color": "#1A5F7A",
- "support": {
- "email": "support@example.com",
- "phone": "+12025550100",
}
}, - "legal_documents_pending": 0
}The current GLP-1 medication, dose and treatment week. Error code treatment_not_found (404) before one is set.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object |
| dose_mg required | number |
| dose_amount | number or null The dose as prescribed, when it is not in mg (for example 20 units of a compounded vial). |
| dose_unit | string or null The unit of |
| phase required | string (TreatmentPhase) Enum: "pending_start" "titration" "maintenance" "taper" "off_ramp" "paused" "stopped" |
| injection_weekday | integer or null |
| started_on | string or null <date> |
| treatment_week | integer or null |
| effective_from | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "medication": {
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection"
}, - "dose_mg": 0.5,
- "dose_amount": null,
- "dose_unit": null,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "treatment_week": 5,
- "effective_from": "2026-10-26T00:00:00Z"
}For patients whose treatment no brand manages: they report the GLP-1 they take. A brand's patients get their treatment from the brand's prescriber,
so this returns 403 treatment_managed_by_brand for them.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| medication_id required | string <uuid> (Uuid) |
| dose_mg required | number ( 0 .. 100 ] |
| phase required | string (TreatmentPhase) Enum: "pending_start" "titration" "maintenance" "taper" "off_ramp" "paused" "stopped" |
| injection_weekday | integer [ 0 .. 6 ] |
| started_on | string <date> |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
required | object |
| dose_mg required | number |
| dose_amount | number or null The dose as prescribed, when it is not in mg (for example 20 units of a compounded vial). |
| dose_unit | string or null The unit of |
| phase required | string (TreatmentPhase) Enum: "pending_start" "titration" "maintenance" "taper" "off_ramp" "paused" "stopped" |
| injection_weekday | integer or null |
| started_on | string or null <date> |
| treatment_week | integer or null |
| effective_from | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "medication_id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
- "dose_mg": 0.5,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28"
}{- "medication": {
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection"
}, - "dose_mg": 0.5,
- "dose_amount": null,
- "dose_unit": null,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "treatment_week": 5,
- "effective_from": "2026-10-26T14:05:00Z"
}The current version of every document that applies to the patient, with whether they accepted it.
On first open, ask for acceptance of each one with required_on_first_open and no accepted_at
before showing coaching screens.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (LegalDocument) |
{- "data": [
- {
- "id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
- "code": "coaching_consent",
- "version": "2026-01",
- "required_on_first_open": true,
- "effective_at": "2026-01-01T00:00:00Z",
- "accepted_at": "2026-10-21T14:57:30Z"
}, - {
- "id": "4f6a8c0e-2b1d-4e3f-8a5b-9c1d3e5f7a9b",
- "code": "privacy",
- "version": "2026-01",
- "required_on_first_open": true,
- "effective_at": "2026-01-01T00:00:00Z",
- "accepted_at": null
}
]
}Records the patient's acceptance in the app. Accepting a document already accepted returns the existing
record with 200. Error code: unknown_legal_document (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| legal_document_id required | string <uuid> (Uuid) |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| document_code required | string |
| document_version required | string |
| accepted_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| withdrawn_at | string or null <date-time> |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| document_code required | string |
| document_version required | string |
| accepted_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| withdrawn_at | string or null <date-time> |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
{- "legal_document_id": "4f6a8c0e-2b1d-4e3f-8a5b-9c1d3e5f7a9b"
}{- "id": "0190f5c8-3c4d-7e5f-9a61-7b8c9d0e1f2a",
- "document_code": "privacy",
- "document_version": "2026-01",
- "accepted_at": "2026-10-22T09:05:12Z",
- "withdrawn_at": null,
- "source": "brand_app"
}Today at a glance, targets for a day, a week's stats and the milestones the patient reached.
What the home screen needs for the patient's current day, in their time zone: targets against what's logged so far, the plan's meals and workouts for the day, habits, the next reminder, recent milestones, and counts of open forms, unread care messages and legal documents to accept.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| treatment_week required | integer or null The same number as |
required | object or null Null when today is not an injection day. |
required | Array of objects (MetricTarget) |
required | Array of objects (TodayMeal) Today's meals from the meal plan, in plan order. Empty without a meal plan. |
required | Array of objects (TodayWorkout) Today's workouts from the workout plan. Empty on a rest day or without a plan. |
required | Array of objects (TodayHabit) |
required | Reminder (object) or null |
| open_forms required | integer Forms waiting for the patient ( |
| unread_care_messages required | integer Across care threads. Always 0 on |
| legal_documents_pending required | integer Required legal documents not accepted yet. When above 0, show |
required | Array of objects (Milestone) Milestones reached in the last 7 days, newest first. |
{- "date": "2026-10-27",
- "treatment_week": 6,
- "injection": {
- "due_today": true,
- "logged": false
}, - "targets": [
- {
- "metric": "protein_g",
- "label": "Protein",
- "unit": "g",
- "target": 110,
- "progress": 72
}, - {
- "metric": "water_ml",
- "label": "Water",
- "unit": "ml",
- "target": 2500,
- "progress": 1250
}
], - "meals": [
- {
- "meal_plan_item_id": "3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01",
- "meal_type": "breakfast",
- "title": "Greek yogurt protein bowl",
- "recipe_id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
- "protein_g": 31,
- "calories_kcal": 320,
- "logged": true
}, - {
- "meal_plan_item_id": "5e3a0b42-7d9c-4f1e-8a83-2c4e6f8a0b13",
- "meal_type": "lunch",
- "title": "Chicken and quinoa bowl",
- "recipe_id": null,
- "protein_g": 38,
- "calories_kcal": 450,
- "logged": false
}
], - "workouts": [
- {
- "workout_plan_item_id": "4d2f9a31-6c8b-4e0d-9f72-1b3c5d7e9f02",
- "title": "Full-body strength A",
- "duration_min": 30,
- "logged": false
}
], - "habits": [
- {
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "key": "protein_each_meal",
- "title": "Protein at every meal",
- "done": false
}
], - "next_reminder": {
- "id": "0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45",
- "kind": "injection",
- "ref_key": null,
- "due_at": "2026-10-27T13:00:00Z",
- "status": "scheduled",
- "recurring": true
}, - "open_forms": 1,
- "unread_care_messages": 2,
- "legal_documents_pending": 0,
- "new_milestones": [ ]
}The targets from the protocols in force on date, with what that day's logs add up to. Metric codes are the
protocol targets' codes, so a new metric needs no app change to show. Error code: date_out_of_range (422) for
a day before the enrollment or in the future.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| date | string <date> Example: date=2026-10-27 A day in the patient's time zone. Defaults to today. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
required | Array of objects (MetricTarget) One per target in the protocols in force that day. Empty before the first protocol is published. |
{- "date": "2026-10-27",
- "targets": [
- {
- "metric": "protein_g",
- "label": "Protein",
- "unit": "g",
- "target": 110,
- "progress": 72
}, - {
- "metric": "water_ml",
- "label": "Water",
- "unit": "ml",
- "target": 2500,
- "progress": 1250
}, - {
- "metric": "fiber_g",
- "label": "Fiber",
- "unit": "g",
- "target": 25,
- "progress": 11
}
]
}A summary of one week of treatment, numbered like treatment_week on GET /v1/patient/treatment: week 1
starts on the treatment's started_on. Defaults to the current week, counted up to today. Error code:
week_not_available (404) for a week before the enrollment, after the current one, or before a treatment is set.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| week_number | integer [ 1 .. 520 ] Example: week_number=6 Omit for the current week. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| week_number required | integer >= 1 The treatment week, the same number as |
| start_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| end_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| days_logged required | integer [ 0 .. 7 ] Days with anything logged. For the current week, counts up to today. |
| logging_streak_days required | integer Days in a row with anything logged, up to the end of the week (or today). |
required | object |
| calories_avg_kcal required | integer or null |
required | object |
required | object |
| steps_avg required | integer or null |
required | object |
required | object |
required | object |
required | Array of objects (Milestone) Milestones reached during the week. |
{- "week_number": 6,
- "start_date": "2026-10-21",
- "end_date": "2026-10-27",
- "days_logged": 5,
- "logging_streak_days": 12,
- "protein": {
- "target_g": 110,
- "avg_g": 96,
- "days_at_target": 4
}, - "calories_avg_kcal": 1480,
- "water": {
- "target_ml": 2500,
- "avg_ml": 2100,
- "days_at_target": 3
}, - "workouts": {
- "count": 3,
- "minutes": 95
}, - "steps_avg": 7400,
- "weight": {
- "start_kg": 96.4,
- "end_kg": 95.6,
- "change_kg": -0.8
}, - "injections": {
- "taken": 1,
- "missed": 0
}, - "symptoms": {
- "days": 2,
- "max_severity": 2
}, - "milestones": [
- {
- "id": "0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f",
- "code": "protein_target_days_7",
- "category": "nutrition",
- "title": "7 days at your protein target",
- "message": "You averaged 112 g a day, up from 74 g in your first week.",
- "achieved_at": "2026-10-25T20:10:00Z",
- "stats": {
- "days": 7,
- "avg_protein_g": 112,
- "first_week_avg_protein_g": 74
}
}
]
}Every milestone the patient reached in this enrollment, newest first. Your server hears about a new one from
patient.milestone_reached, so the app can celebrate it.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Milestone) Newest first. At most one per code. |
{- "data": [
- {
- "id": "0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f",
- "code": "protein_target_days_7",
- "category": "nutrition",
- "title": "7 days at your protein target",
- "message": "You averaged 112 g a day, up from 74 g in your first week.",
- "achieved_at": "2026-10-25T20:10:00Z",
- "stats": {
- "days": 7,
- "avg_protein_g": 112,
- "first_week_avg_protein_g": 74
}
}, - {
- "id": "0190f5d9-1f2a-7b3c-8d4e-5f6a7b8c9d0e",
- "code": "logging_streak_7",
- "category": "logging",
- "title": "7 days of logging in a row",
- "message": "A full week of logging. That's what makes your plan work.",
- "achieved_at": "2026-10-22T19:30:00Z",
- "stats": {
- "days": 7
}
}
]
}The milestone with the patient's own numbers, for a celebration screen after patient.milestone_reached.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| milestone_id required | string <uuid> Example: 0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) | ||||||||||||||||||
| code required | string (MilestoneCode) Enum: "first_meal_logged" "logging_streak_7" "logging_streak_30" "protein_target_days_7" "protein_target_days_30" "workouts_10" "workouts_50" "injections_on_time_4" "injections_on_time_12" "week_4_completed" "week_12_completed" "weight_loss_5_pct" "weight_loss_10_pct" "goal_weight_reached" What the patient reached. New codes may be added; show a generic celebration for a code you don't know.
| ||||||||||||||||||
| category required | string Enum: "logging" "nutrition" "activity" "treatment" "program" "weight" | ||||||||||||||||||
| title required | string A short headline in the patient's language. | ||||||||||||||||||
| message required | string One personal line built from | ||||||||||||||||||
| achieved_at required | string <date-time> (Timestamp) ISO 8601 in UTC. | ||||||||||||||||||
required | object The patient's numbers behind the milestone. Keys depend on the code. |
{- "id": "0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f",
- "code": "weight_loss_5_pct",
- "category": "weight",
- "title": "5% of your starting weight",
- "message": "You're down 5.1 kg since your first weigh-in. Keep the protein up to protect your muscle.",
- "achieved_at": "2026-10-25T08:05:00Z",
- "stats": {
- "start_weight_kg": 100.7,
- "current_weight_kg": 95.6,
- "change_pct": -5.1
}
}Forms sent to the patient, newest first. By default only open ones (sent, in_progress).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| status | Array of strings (FormAssignmentStatus) Default: "sent,in_progress" Items Enum: "pending" "sent" "in_progress" "completed" "expired" "cancelled" Example: status=sent,in_progress Filter by one or more statuses, comma-separated. |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (FormAssignmentSummary) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form": {
- "code": "glp1_onboarding",
- "version": 1,
- "purpose": "onboarding",
- "title": "Coaching Onboarding"
}, - "status": "sent",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "sent_at": "2026-10-21T15:00:00Z",
- "completed_at": null
}
], - "next_cursor": null
}The form's fields at the version it was sent with, the prefill from the brand's intake and any saved draft.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| form_assignment_id required | string <uuid> Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
required | object (FormSummary) |
| status required | string (FormAssignmentStatus) Enum: "pending" "sent" "in_progress" "completed" "expired" "cancelled" |
| blocks_onboarding required | boolean Onboarding completes only when every blocking form is completed. |
| due_at | string or null <date-time> |
| sent_at | string or null <date-time> |
| completed_at | string or null <date-time> |
required | Array of objects (FormField) |
required | object (Answers) Values the brand's intake already provided. Show them as defaults. |
Answers (object) or null | |
Answers (object) or null Submitted answers, once completed. | |
EligibilityResult (object) or null Eligibility forms only, once completed. |
{- "id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form": {
- "code": "glp1_onboarding",
- "version": 1,
- "purpose": "onboarding",
- "title": "Coaching Onboarding"
}, - "status": "in_progress",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "sent_at": "2026-10-21T15:00:00Z",
- "completed_at": null,
- "fields": [
- {
- "id": "weight_kg",
- "type": "number",
- "label": "Current weight",
- "unit": "kg",
- "required": true
}, - {
- "id": "primary_goal",
- "type": "select",
- "label": "Primary goal",
- "options": [
- "lose_weight",
- "preserve_muscle",
- "manage_side_effects",
- "build_habits"
], - "required": true
}
], - "prefill": {
- "weight_kg": 96.2
}, - "draft_answers": {
- "primary_goal": "preserve_muscle"
}, - "answers": null,
- "result": null
}Saves partial answers so the patient can finish later; the status becomes in_progress.
Each call replaces the previous draft. Error code: form_not_open (409) once completed, expired or cancelled.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| form_assignment_id required | string <uuid> Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2 |
required | object (Answers) Answers keyed by field |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
required | object (FormSummary) |
| status required | string (FormAssignmentStatus) Enum: "pending" "sent" "in_progress" "completed" "expired" "cancelled" |
| blocks_onboarding required | boolean Onboarding completes only when every blocking form is completed. |
| due_at | string or null <date-time> |
| sent_at | string or null <date-time> |
| completed_at | string or null <date-time> |
required | Array of objects (FormField) |
required | object (Answers) Values the brand's intake already provided. Show them as defaults. |
Answers (object) or null | |
Answers (object) or null Submitted answers, once completed. | |
EligibilityResult (object) or null Eligibility forms only, once completed. |
{- "answers": {
- "primary_goal": "preserve_muscle"
}
}{- "id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form": {
- "code": "glp1_onboarding",
- "version": 1,
- "purpose": "onboarding",
- "title": "Coaching Onboarding"
}, - "status": "in_progress",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "sent_at": "2026-10-21T15:00:00Z",
- "completed_at": null,
- "fields": [
- {
- "id": "weight_kg",
- "type": "number",
- "label": "Current weight",
- "unit": "kg",
- "required": true
}, - {
- "id": "primary_goal",
- "type": "select",
- "label": "Primary goal",
- "options": [
- "lose_weight",
- "preserve_muscle",
- "manage_side_effects",
- "build_habits"
], - "required": true
}
], - "prefill": {
- "weight_kg": 96.2
}, - "draft_answers": {
- "primary_goal": "preserve_muscle"
}, - "answers": null,
- "result": null
}Validates every answer against the fields (required, type, options, range) and completes the form.
When the last blocking form is completed the enrollment moves to onboarding_completed
(onboarding.completed). Answers to health profile questions (allergies, dietary restrictions, activity level,
cooking, lifestyle and so on) update the patient's profile, readable with GET /v1/patient. An eligibility
form returns its result.
Error codes: validation_failed (422, with a pointer per field such as /answers/weight_kg), form_not_open (409).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| form_assignment_id required | string <uuid> Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
required | object (Answers) Answers keyed by field |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
required | object (FormSummary) |
| status required | string (FormAssignmentStatus) Enum: "pending" "sent" "in_progress" "completed" "expired" "cancelled" |
| blocks_onboarding required | boolean Onboarding completes only when every blocking form is completed. |
| due_at | string or null <date-time> |
| sent_at | string or null <date-time> |
| completed_at | string or null <date-time> |
required | Array of objects (FormField) |
required | object (Answers) Values the brand's intake already provided. Show them as defaults. |
Answers (object) or null | |
Answers (object) or null Submitted answers, once completed. | |
EligibilityResult (object) or null Eligibility forms only, once completed. |
{- "answers": {
- "weight_kg": 96.2,
- "primary_goal": "preserve_muscle"
}
}{- "id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form": {
- "code": "glp1_onboarding",
- "version": 1,
- "purpose": "onboarding",
- "title": "Coaching Onboarding"
}, - "status": "completed",
- "blocks_onboarding": true,
- "due_at": "2026-10-24T15:00:00Z",
- "sent_at": "2026-10-21T15:00:00Z",
- "completed_at": "2026-10-22T09:30:00Z",
- "fields": [
- {
- "id": "weight_kg",
- "type": "number",
- "label": "Current weight",
- "unit": "kg",
- "required": true
}, - {
- "id": "primary_goal",
- "type": "select",
- "label": "Primary goal",
- "options": [
- "lose_weight",
- "preserve_muscle",
- "manage_side_effects",
- "build_habits"
], - "required": true
}
], - "prefill": {
- "weight_kg": 96.2
}, - "draft_answers": null,
- "answers": {
- "weight_kg": 96.2,
- "primary_goal": "preserve_muscle"
}, - "result": null
}Meals, workouts, symptoms, injections and daily values. Weight and body measures go on the daily log. Logs made in the app can be corrected or deleted; deleted logs leave lists and rollups.
One row per day with the patient's daily values and the rollups from meals, workouts and symptoms.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| from | string <date> Example: from=2026-10-20 First day to include, in the patient's time zone. |
| to | string <date> Example: to=2026-10-26 Last day to include, in the patient's time zone. At most 92 days after |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (DailyLog) One entry per day that has any data, newest first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "log_date": "2026-10-26",
- "water_ml": 1800,
- "weight_kg": 94.8,
- "waist_cm": null,
- "hip_cm": null,
- "body_fat_pct": null,
- "steps": 7400,
- "sleep_minutes": 430,
- "sleep_quality": 4,
- "mood": 4,
- "stress": 2,
- "energy": 3,
- "habits_done": {
- "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90:protein_each_meal": true
}, - "metrics": { },
- "protein_g": 92.5,
- "calories_kcal": 1450,
- "fiber_g": 21,
- "workout_minutes": 35,
- "max_symptom_severity": 2,
- "sources": [
- "brand_app"
], - "updated_at": "2026-10-26T20:15:00Z"
}
], - "next_cursor": null
}Water, weight and body measures, steps, sleep, mood, stress, energy, habit check-offs and any other metric for
one day. Only the fields sent change. Dates more than 30 days back or in the future are refused
(log_date_out_of_range, 422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_date required | string <date> Example: 2026-10-26 The day, in the patient's time zone. |
| water_ml | integer or null [ 0 .. 20000 ] |
| weight_kg | number or null [ 20 .. 400 ] |
| waist_cm | number or null [ 30 .. 300 ] |
| hip_cm | number or null [ 30 .. 300 ] |
| body_fat_pct | number or null [ 1 .. 80 ] |
| steps | integer or null [ 0 .. 200000 ] |
| sleep_minutes | integer or null [ 0 .. 1440 ] |
| sleep_quality | integer or null (Score) [ 1 .. 5 ] |
| mood | integer or null (Score) [ 1 .. 5 ] |
| stress | integer or null (Score) [ 1 .. 5 ] |
| energy | integer or null (Score) [ 1 .. 5 ] |
object Habit check-offs keyed | |
object Any other daily metric, keyed by a metric code. New codes need no API change. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| water_ml | integer or null |
| weight_kg | number or null |
| waist_cm | number or null |
| hip_cm | number or null |
| body_fat_pct | number or null |
| steps | integer or null |
| sleep_minutes | integer or null |
| sleep_quality | integer or null (Score) [ 1 .. 5 ] |
| mood | integer or null (Score) [ 1 .. 5 ] |
| stress | integer or null (Score) [ 1 .. 5 ] |
| energy | integer or null (Score) [ 1 .. 5 ] |
required | object |
required | object |
| protein_g | number or null Rollup of the day's meal logs. |
| calories_kcal | integer or null |
| fiber_g | number or null |
| workout_minutes | integer or null Rollup of the day's workout logs. |
| max_symptom_severity | integer or null Worst symptom severity logged that day. |
| sources required | Array of strings (RecordSource) Items Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" |
| updated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "water_ml": 1800,
- "weight_kg": 94.8,
- "mood": 4,
- "habits_done": {
- "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90:protein_each_meal": true
}
}{- "log_date": "2026-10-26",
- "water_ml": 1800,
- "weight_kg": 94.8,
- "waist_cm": null,
- "hip_cm": null,
- "body_fat_pct": null,
- "steps": null,
- "sleep_minutes": null,
- "sleep_quality": null,
- "mood": 4,
- "stress": null,
- "energy": null,
- "habits_done": {
- "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90:protein_each_meal": true
}, - "metrics": { },
- "protein_g": 48,
- "calories_kcal": 720,
- "fiber_g": 9,
- "workout_minutes": null,
- "max_symptom_severity": null,
- "sources": [
- "brand_app"
], - "updated_at": "2026-10-26T14:05:00Z"
}Returns a pre-signed URL. Upload the photo there, then send upload_id as photo_upload_id on the meal log.
An upload not attached to a meal within 1 hour is deleted.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| content_type required | string Enum: "image/jpeg" "image/png" "image/heic" |
| content_length required | integer [ 1 .. 10485760 ] Bytes, at most 10 MB. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| upload_id required | string <uuid> (Uuid) |
| upload_url required | string <uri> Pre-signed URL. Upload the bytes with |
| method required | string Value: "PUT" |
required | object Headers to send with the upload, exactly as given. |
| expires_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "content_type": "image/jpeg",
- "content_length": 1843200
}{- "upload_id": "8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e",
- "method": "PUT",
- "headers": {
- "Content-Type": "image/jpeg"
}, - "expires_at": "2026-10-26T14:15:00Z"
}The AI Coach estimates the meal in an uploaded photo: what it is, its ingredients and their macros. Show the
result as an editable form, then save it with POST /v1/patient/meals, sending the same photo_upload_id and
entry_method: photo_ai. Takes a few seconds. Asking again for the same upload returns the same analysis.
Counts toward the plan's daily photo analysis limit (15 by default). Error codes: upload_not_found (404),
ai_paused (409) when the AI Coach is paused for the plan, daily_photo_analysis_cap_reached (429),
ai_unavailable (503).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| upload_id required | string <uuid> Example: 8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e From |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| upload_id required | string <uuid> (Uuid) |
| description | string or null |
MealType (string) or null A guess from the photo and the time; the patient can change it. | |
required | Array of objects (Ingredient) Empty when no food was recognized. |
required | object |
| confidence required | string Enum: "low" "medium" "high" Show the estimate as a starting point when |
| note | string or null Something the patient should check, for example a sauce the photo can't show. |
{- "upload_id": "8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e",
- "description": "Grilled chicken salad with quinoa",
- "meal_type": "lunch",
- "ingredients": [
- {
- "name": "Grilled chicken breast",
- "quantity": 120,
- "unit": "g",
- "protein_g": 31,
- "calories_kcal": 200,
- "carbs_g": 0,
- "fat_g": 4,
- "fiber_g": 0
}, - {
- "name": "Quinoa, cooked",
- "quantity": 90,
- "unit": "g",
- "protein_g": 4,
- "calories_kcal": 110,
- "carbs_g": 19,
- "fat_g": 2,
- "fiber_g": 3
}, - {
- "name": "Mixed greens and tomato",
- "quantity": 80,
- "unit": "g",
- "protein_g": 1,
- "calories_kcal": 20,
- "carbs_g": 4,
- "fat_g": 0,
- "fiber_g": 2
}
], - "totals": {
- "protein_g": 36,
- "calories_kcal": 330,
- "carbs_g": 23,
- "fat_g": 6,
- "fiber_g": 5
}, - "confidence": "medium",
- "note": "The dressing isn't visible; add it if you used one."
}Foods the patient logged before, most recently logged first, 10 at a time; follow next_cursor for the next 10.
Picking one pre-fills a meal ingredient with the amount and macros from the last time. Correcting or deleting a
meal updates the list.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| limit | integer [ 1 .. 50 ] Default: 10 Example: limit=10 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (RecentFood) Most recently logged first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5e2-3b4c-7d5e-8f60-7a8b9c0d1e2f",
- "name": "Greek yogurt, plain, nonfat",
- "quantity": 170,
- "unit": "g",
- "protein_g": 17,
- "calories_kcal": 100,
- "carbs_g": 6,
- "fat_g": 0.7,
- "fiber_g": 0,
- "times_logged": 9,
- "last_logged_at": "2026-10-27T07:45:00Z"
}, - {
- "id": "0190f5e2-1a2b-7c3d-9e4f-5a6b7c8d9e0f",
- "name": "Chicken and quinoa bowl",
- "quantity": null,
- "unit": null,
- "protein_g": 38,
- "calories_kcal": 450,
- "carbs_g": null,
- "fat_g": null,
- "fiber_g": null,
- "times_logged": 3,
- "last_logged_at": "2026-10-26T12:30:00Z"
}
], - "next_cursor": "eyJpZCI6IjAxOTBmNWUyIn0"
}Macros are optional; send what you have. Updates the day's protein, calorie and fiber rollups.
Error codes: upload_not_found (422) for an unknown or expired photo_upload_id, meal_plan_item_not_found (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| eaten_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (Uuid) Set when the patient ate a meal from their plan. |
| description | string <= 1000 characters |
| photo_upload_id | string <uuid> (Uuid) From |
| entry_method | string Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number [ 0 .. 500 ] |
| calories_kcal | integer [ 0 .. 10000 ] |
| carbs_g | number >= 0 |
| fat_g | number >= 0 |
| fiber_g | number >= 0 |
| sugar_g | number >= 0 |
| sodium_mg | integer >= 0 |
object Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (Ingredient) <= 50 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your id for this log. A second log with the same id returns the first one instead of a duplicate. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| eaten_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (Uuid) Set when the patient ate a meal from their plan. |
| description | string <= 1000 characters |
| entry_method | string Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number [ 0 .. 500 ] |
| calories_kcal | integer [ 0 .. 10000 ] |
| carbs_g | number >= 0 |
| fat_g | number >= 0 |
| fiber_g | number >= 0 |
| sugar_g | number >= 0 |
| sodium_mg | integer >= 0 |
object Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (Ingredient) <= 50 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your id for this log. A second log with the same id returns the first one instead of a duplicate. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| photo_url | string or null <uri> Short-lived link to the photo (expires within minutes). Do not store it. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "eaten_at": "2026-10-26T12:30:00Z",
- "meal_type": "lunch",
- "description": "Greek yogurt with berries and almonds",
- "photo_upload_id": "8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e",
- "entry_method": "photo",
- "protein_g": 24,
- "calories_kcal": 310,
- "fiber_g": 6
}{- "id": "0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60",
- "eaten_at": "2026-10-26T12:30:00Z",
- "log_date": "2026-10-26",
- "meal_type": "lunch",
- "description": "Greek yogurt with berries and almonds",
- "entry_method": "photo",
- "protein_g": 24,
- "calories_kcal": 310,
- "fiber_g": 6,
- "source": "brand_app",
- "created_at": "2026-10-26T12:31:04Z",
- "updated_at": "2026-10-26T12:31:04Z"
}Meal logs in a date range, newest first.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| from | string <date> Example: from=2026-10-20 First day to include, in the patient's time zone. |
| to | string <date> Example: to=2026-10-26 Last day to include, in the patient's time zone. At most 92 days after |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (MealLog) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60",
- "eaten_at": "2026-10-26T12:30:00Z",
- "log_date": "2026-10-26",
- "meal_type": "lunch",
- "description": "Greek yogurt with berries and almonds",
- "entry_method": "manual",
- "protein_g": 24,
- "calories_kcal": 310,
- "photo_url": null,
- "source": "brand_app",
- "created_at": "2026-10-26T12:31:04Z",
- "updated_at": "2026-10-26T12:31:04Z"
}
], - "next_cursor": null
}Changes a meal the patient logged. Only logs made in the app can be changed; a log the brand's server sent
returns 403 log_not_owned. The previous version is kept in the record's history, and the day's rollups are
recalculated (for both days if the meal moves to another day).
Error codes: log_not_owned (403), upload_not_found, meal_plan_item_not_found (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
| eaten_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (meal_plan_item_id) Set when the patient ate a meal from their plan. |
| description | string (description) <= 1000 characters |
| photo_upload_id | string <uuid> (photo_upload_id) From |
| entry_method | string (entry_method) Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number (protein_g) [ 0 .. 500 ] |
| calories_kcal | integer (calories_kcal) [ 0 .. 10000 ] |
| carbs_g | number (carbs_g) >= 0 |
| fat_g | number (fat_g) >= 0 |
| fiber_g | number (fiber_g) >= 0 |
| sugar_g | number (sugar_g) >= 0 |
| sodium_mg | integer (sodium_mg) >= 0 |
object (nutrients) Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (ingredients) <= 50 items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| eaten_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| meal_plan_item_id | string <uuid> (Uuid) Set when the patient ate a meal from their plan. |
| description | string <= 1000 characters |
| entry_method | string Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan" How it was entered. |
| protein_g | number [ 0 .. 500 ] |
| calories_kcal | integer [ 0 .. 10000 ] |
| carbs_g | number >= 0 |
| fat_g | number >= 0 |
| fiber_g | number >= 0 |
| sugar_g | number >= 0 |
| sodium_mg | integer >= 0 |
object Vitamins, minerals and anything else, keyed by nutrient code. | |
Array of objects (Ingredient) <= 50 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your id for this log. A second log with the same id returns the first one instead of a duplicate. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| photo_url | string or null <uri> Short-lived link to the photo (expires within minutes). Do not store it. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "protein_g": 30,
- "calories_kcal": 360
}{- "id": "0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60",
- "eaten_at": "2026-10-26T12:30:00Z",
- "log_date": "2026-10-26",
- "meal_type": "lunch",
- "description": "Greek yogurt with berries and almonds",
- "photo_url": null,
- "entry_method": "manual",
- "protein_g": 30,
- "calories_kcal": 360,
- "fiber_g": 6,
- "source": "brand_app",
- "created_at": "2026-10-26T12:31:04Z",
- "updated_at": "2026-10-26T13:02:00Z"
}Deletes a meal logged by mistake. It leaves lists and the day's rollups at once; its history is kept until the
brand's retention period ends. Deleting it again returns 204. Only logs made in the app can be deleted.
Error code: log_not_owned (403).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Updates the day's workout minutes. Link workout_plan_item_id when the workout came from the plan.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| started_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type required | string <= 50 characters For example |
| duration_min | integer [ 1 .. 600 ] |
| completed | boolean Default: true |
| perceived_effort | integer [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (Uuid) |
Array of objects (WorkoutSet) <= 100 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| started_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type required | string <= 50 characters For example |
| duration_min | integer [ 1 .. 600 ] |
| completed | boolean Default: true |
| perceived_effort | integer [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (Uuid) |
Array of objects (WorkoutSet) <= 100 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "started_at": "2026-10-26T07:00:00Z",
- "activity_type": "strength",
- "duration_min": 35,
- "perceived_effort": 6,
- "workout_plan_item_id": "1b3d5f7a-9c2e-4a6b-8d0f-2e4a6c8e0b2d",
- "sets": [
- {
- "exercise_id": "5a7c9e1b-3d5f-4a8c-9e2b-4d6f8a0c2e4f",
- "name": "Goblet squat",
- "reps": 10,
- "weight_kg": 12
}
]
}{- "id": "0190f5c9-6e7f-7081-9ba3-2c3d4e5f6071",
- "started_at": "2026-10-26T07:00:00Z",
- "log_date": "2026-10-26",
- "activity_type": "strength",
- "duration_min": 35,
- "completed": true,
- "perceived_effort": 6,
- "workout_plan_item_id": "1b3d5f7a-9c2e-4a6b-8d0f-2e4a6c8e0b2d",
- "sets": [
- {
- "exercise_id": "5a7c9e1b-3d5f-4a8c-9e2b-4d6f8a0c2e4f",
- "name": "Goblet squat",
- "reps": 10,
- "weight_kg": 12
}
], - "source": "brand_app",
- "created_at": "2026-10-26T07:40:00Z",
- "updated_at": "2026-10-26T07:40:00Z"
}Workout logs in a date range, newest first.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| from | string <date> Example: from=2026-10-20 First day to include, in the patient's time zone. |
| to | string <date> Example: to=2026-10-26 Last day to include, in the patient's time zone. At most 92 days after |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (WorkoutLog) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c9-6e7f-7081-9ba3-2c3d4e5f6071",
- "started_at": "2026-10-26T07:00:00Z",
- "log_date": "2026-10-26",
- "activity_type": "walk",
- "duration_min": 30,
- "completed": true,
- "source": "brand_app",
- "created_at": "2026-10-26T07:40:00Z",
- "updated_at": "2026-10-26T07:40:00Z"
}
], - "next_cursor": null
}Changes a workout the patient logged. Only logs made in the app can be changed; a log the brand's server sent
returns 403 log_not_owned. The previous version is kept in the record's history, and the day's rollups are
recalculated (for both days if the workout moves to another day).
Error codes: log_not_owned (403), workout_plan_item_not_found (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
| started_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type | string (activity_type) <= 50 characters For example |
| duration_min | integer (duration_min) [ 1 .. 600 ] |
| completed | boolean (completed) |
| perceived_effort | integer (perceived_effort) [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (workout_plan_item_id) |
Array of objects (sets) <= 100 items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| started_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| activity_type required | string <= 50 characters For example |
| duration_min | integer [ 1 .. 600 ] |
| completed | boolean Default: true |
| perceived_effort | integer [ 1 .. 10 ] How hard the workout felt, from 1 to 10. |
| workout_plan_item_id | string <uuid> (Uuid) |
Array of objects (WorkoutSet) <= 100 items | |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "duration_min": 45
}{- "id": "0190f5c9-6e7f-7081-9ba3-2c3d4e5f6071",
- "started_at": "2026-10-26T07:00:00Z",
- "log_date": "2026-10-26",
- "activity_type": "strength",
- "duration_min": 45,
- "completed": true,
- "perceived_effort": 6,
- "source": "brand_app",
- "created_at": "2026-10-26T07:40:00Z",
- "updated_at": "2026-10-26T08:10:00Z"
}Deletes a workout logged by mistake. It leaves lists and the day's rollups at once; its history is kept until the
brand's retention period ends. Deleting it again returns 204. Only logs made in the app can be deleted.
Error code: log_not_owned (403).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Severity 1 (mild) to 5 (severe). Alert rules run on every symptom: a red flag or a severe GI episode raises a
clinical flag (to the coach on the coached plan, to the brand's prescriber on ai_only).
When the response has is_red_flag: true, show the emergency guidance (call 911, or 988 for a mental health
crisis) before anything else. Coaching is not emergency care.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| occurred_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom required | string^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity required | integer [ 1 .. 5 ] |
| note | string <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| occurred_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom required | string^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity required | integer [ 1 .. 5 ] |
| note | string <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| is_red_flag required | boolean Set by TeraRx from the symptom and severity (see |
| days_since_injection | integer or null |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "occurred_at": "2026-10-26T19:45:00Z",
- "symptom": "nausea",
- "severity": 2,
- "note": "Mild, after dinner"
}{- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T19:45:00Z",
- "log_date": "2026-10-26",
- "symptom": "nausea",
- "severity": 2,
- "note": "Mild, after dinner",
- "is_red_flag": false,
- "days_since_injection": 0,
- "source": "brand_app",
- "created_at": "2026-10-26T19:46:10Z",
- "updated_at": "2026-10-26T19:46:10Z"
}Symptom logs in a date range, newest first.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| from | string <date> Example: from=2026-10-20 First day to include, in the patient's time zone. |
| to | string <date> Example: to=2026-10-26 Last day to include, in the patient's time zone. At most 92 days after |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (SymptomLog) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T19:45:00Z",
- "log_date": "2026-10-26",
- "symptom": "nausea",
- "severity": 2,
- "is_red_flag": false,
- "days_since_injection": 0,
- "source": "brand_app",
- "created_at": "2026-10-26T19:46:10Z",
- "updated_at": "2026-10-26T19:46:10Z"
}
], - "next_cursor": null
}Changes a symptom the patient logged. Only logs made in the app can be changed; a log the brand's server sent
returns 403 log_not_owned. The previous version is kept in the record's history, and the day's rollups are
recalculated (for both days if the symptom moves to another day).
Editing or deleting a symptom never closes a clinical flag or lowers the risk level; only the prescriber's resolve
does. A correction that makes a symptom more severe runs the alert rules again, like a new log, so it can raise a
flag. A flag raised by a symptom deleted later shows deleted_at on its triggering record.
Error code: log_not_owned (403).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
| occurred_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom | string (symptom) ^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity | integer (severity) [ 1 .. 5 ] |
| note | string (note) <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| occurred_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| symptom required | string^[a-z][a-z0-9_]*$ A symptom code, for example |
| severity required | integer [ 1 .. 5 ] |
| note | string <= 1000 characters The patient's own words. Read by the care team and, on a clinical flag, the prescriber. |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| is_red_flag required | boolean Set by TeraRx from the symptom and severity (see |
| days_since_injection | integer or null |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "severity": 3,
- "note": "Worse after dinner than I first logged"
}{- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T19:45:00Z",
- "log_date": "2026-10-26",
- "symptom": "nausea",
- "severity": 3,
- "note": "Worse after dinner than I first logged",
- "is_red_flag": false,
- "days_since_injection": 2,
- "source": "brand_app",
- "created_at": "2026-10-26T19:46:10Z",
- "updated_at": "2026-10-26T20:30:00Z"
}Deletes a symptom logged by mistake. It leaves lists and the day's rollups at once; its history is kept until the
brand's retention period ends. Deleting it again returns 204. Only logs made in the app can be deleted.
Editing or deleting a symptom never closes a clinical flag or lowers the risk level; only the prescriber's resolve
does. A correction that makes a symptom more severe runs the alert rules again, like a new log, so it can raise a
flag. A flag raised by a symptom deleted later shows deleted_at on its triggering record.
Error code: log_not_owned (403).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}A dose taken (injected_at) or missed (missed: true). A missed dose can raise a missed_injection alert.
Error code: injection_incomplete (422) when the log is neither taken nor missed.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| injected_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed | boolean Default: false |
| scheduled_for | string <date> |
| dose_mg | number > 0 |
| site | string For example |
| note | string <= 1000 characters |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| injected_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed required | boolean Default: false |
| scheduled_for | string <date> |
| dose_mg | number > 0 |
| site | string For example |
| note | string <= 1000 characters |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "injected_at": "2026-10-26T08:00:00Z",
- "scheduled_for": "2026-10-26",
- "dose_mg": 0.5,
- "site": "abdomen_left"
}{- "id": "0190f5c9-8091-72a3-9dc5-4e5f60718293",
- "injected_at": "2026-10-26T08:00:00Z",
- "missed": false,
- "scheduled_for": "2026-10-26",
- "log_date": "2026-10-26",
- "dose_mg": 0.5,
- "site": "abdomen_left",
- "source": "brand_app",
- "created_at": "2026-10-26T08:01:30Z",
- "updated_at": "2026-10-26T08:01:30Z"
}Injection logs in a date range, newest first.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| from | string <date> Example: from=2026-10-20 First day to include, in the patient's time zone. |
| to | string <date> Example: to=2026-10-26 Last day to include, in the patient's time zone. At most 92 days after |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of InjectionLog (object) or InjectionLog (object) (InjectionLog) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c9-8091-72a3-9dc5-4e5f60718293",
- "injected_at": "2026-10-26T08:00:00Z",
- "missed": false,
- "scheduled_for": "2026-10-26",
- "log_date": "2026-10-26",
- "dose_mg": 0.5,
- "site": "abdomen_left",
- "source": "brand_app",
- "created_at": "2026-10-26T08:01:30Z",
- "updated_at": "2026-10-26T08:01:30Z"
}
], - "next_cursor": null
}Changes an injection the patient logged. Only logs made in the app can be changed; a log the brand's server sent
returns 403 log_not_owned. The previous version is kept in the record's history, and the day's rollups are
recalculated (for both days if the dose moves to another day).
Changing a missed dose to taken (or back) does not close an alert already raised for it.
Error codes: log_not_owned (403), injection_incomplete (422) when the result is neither taken nor missed.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
| injected_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed | boolean (missed) |
| scheduled_for | string <date> (scheduled_for) |
| dose_mg | number (dose_mg) > 0 |
| site | string (site) For example |
| note | string (note-2) <= 1000 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| injected_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| missed required | boolean Default: false |
| scheduled_for | string <date> |
| dose_mg | number > 0 |
| site | string For example |
| note | string <= 1000 characters |
| external_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Your own identifier, printable ASCII without spaces. |
| id required | string <uuid> (Uuid) |
| log_date required | string <date> (LocalDate) A calendar date in the patient's time zone. |
| source required | string (RecordSource) Enum: "app" "brand_app" "brand_api" "wearable" "coach" "ai" "system" Set by the API from the caller. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) When the log was last corrected; the same as |
{- "missed": false,
- "injected_at": "2026-10-26T08:00:00Z",
- "dose_mg": 0.5
}{- "id": "0190f5c9-8091-72a3-9dc5-4e5f60718293",
- "injected_at": "2026-10-26T08:00:00Z",
- "missed": false,
- "scheduled_for": "2026-10-26",
- "dose_mg": 0.5,
- "site": "abdomen_left",
- "log_date": "2026-10-26",
- "source": "brand_app",
- "created_at": "2026-10-26T08:01:30Z",
- "updated_at": "2026-10-26T09:15:00Z"
}Deletes an injection logged by mistake. It leaves lists and the day's rollups at once; its history is kept until the
brand's retention period ends. Deleting it again returns 204. Only logs made in the app can be deleted.
Changing a missed dose to taken (or back) does not close an alert already raised for it.
Error code: log_not_owned (403).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| log_id required | string <uuid> Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60 The log's |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Published protocols, meal plans and workout plans, read only. Drafts are never visible to patients.
At most one active protocol per type (nutrition, fitness, sleep, stress, lifestyle, supplements).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (ProtocolSummary) |
{- "data": [
- {
- "id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 2,
- "status": "active",
- "mode": "full_plan",
- "title": "Protein-first nutrition, titration weeks 5-8",
- "summary": "Hit 110 g of protein a day and keep meals small while the dose goes up.",
- "published_at": "2026-10-25T08:00:00Z",
- "published_by": "coach"
}, - {
- "id": "0190f5c6-9cad-7e12-8f34-5a6b7c8d9e01",
- "type": "fitness",
- "version": 1,
- "status": "active",
- "mode": "full_plan",
- "title": "Muscle-preserving strength, weeks 5-8",
- "summary": "Two short strength sessions and daily walks.",
- "published_at": "2026-10-23T16:45:00Z",
- "published_by": "coach"
}
]
}The patient's active protocol of the given type. Error code: protocol_not_found (404) when there is no active protocol of this type.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| protocol_type required | string (ProtocolType) Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements" Example: nutrition |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| type required | string (ProtocolType) Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements" |
| version required | integer |
| status required | string Enum: "active" "superseded" Drafts are never shown to patients. |
| mode required | string Enum: "guideline" "full_plan"
|
| title required | string |
| summary | string or null |
| published_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| published_by required | string (PublishedBy) Enum: "coach" "ai_coach"
|
| change_reason | string or null or string or null Why this version replaced the one before it. Null on version 1. |
required | object |
{- "id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 2,
- "status": "active",
- "mode": "full_plan",
- "title": "Protein-first nutrition, titration weeks 5-8",
- "summary": "Hit 110 g of protein a day and keep meals small while the dose goes up.",
- "published_at": "2026-10-25T08:00:00Z",
- "published_by": "coach",
- "change_reason": "Dose increased to 0.5 mg",
- "content": {
- "markdown": "## This week\nAim for 30 g of protein at each meal and sip water through the day.\n",
- "targets": [
- {
- "metric": "protein_g",
- "value": 110,
- "unit": "g",
- "label": "Protein per day"
}, - {
- "metric": "water_ml",
- "value": 2500,
- "unit": "ml",
- "label": "Water per day"
}
], - "habits": [
- {
- "key": "protein_each_meal",
- "title": "Protein at every meal",
- "kind": "checkoff",
- "target_value": null,
- "unit": null
}
], - "meal_plan_id": "0190f5ca-1a2b-7c3d-8e4f-5a6b7c8d9e01",
- "workout_plan_id": null
}
}The active version and the superseded ones before it.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| protocol_type required | string (ProtocolType) Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements" Example: nutrition |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (ProtocolSummary) Newest version first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "type": "nutrition",
- "version": 2,
- "status": "active",
- "mode": "full_plan",
- "title": "Protein-first nutrition, titration weeks 5-8",
- "summary": "Hit 110 g of protein a day and keep meals small while the dose goes up.",
- "published_at": "2026-10-25T08:00:00Z",
- "published_by": "coach"
}, - {
- "id": "0190f5c6-7a8b-7c90-8d12-3e4f5a6b7c8d",
- "type": "nutrition",
- "version": 1,
- "status": "superseded",
- "mode": "full_plan",
- "title": "Protein-first nutrition, weeks 1-4",
- "summary": "Build the protein habit while the dose starts low.",
- "published_at": "2026-10-23T16:45:00Z",
- "published_by": "coach"
}
], - "next_cursor": null
}Any published version, active or superseded.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| protocol_type required | string (ProtocolType) Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements" Example: nutrition |
| version required | integer >= 1 Example: 2 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| type required | string (ProtocolType) Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements" |
| version required | integer |
| status required | string Enum: "active" "superseded" Drafts are never shown to patients. |
| mode required | string Enum: "guideline" "full_plan"
|
| title required | string |
| summary | string or null |
| published_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| published_by required | string (PublishedBy) Enum: "coach" "ai_coach"
|
| change_reason | string or null or string or null Why this version replaced the one before it. Null on version 1. |
required | object |
{- "id": "0190f5c6-7a8b-7c90-8d12-3e4f5a6b7c8d",
- "type": "nutrition",
- "version": 1,
- "status": "superseded",
- "mode": "full_plan",
- "title": "Protein-first nutrition, weeks 1-4",
- "summary": "Build the protein habit while the dose starts low.",
- "published_at": "2026-10-23T16:45:00Z",
- "published_by": "coach",
- "change_reason": null,
- "content": {
- "markdown": "## Getting started\nStart each day with a protein breakfast.\n",
- "targets": [
- {
- "metric": "protein_g",
- "value": 100,
- "unit": "g",
- "label": "Protein per day"
}
], - "habits": [
- {
- "key": "protein_each_meal",
- "title": "Protein at every meal",
- "kind": "checkoff",
- "target_value": null,
- "unit": null
}
], - "meal_plan_id": null,
- "workout_plan_id": null
}
}The active meal plan, day by day, including any swapped meals. Error code meal_plan_not_found (404).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| protocol_id | string or null <uuid> |
| version required | integer |
| status required | string Enum: "active" "superseded" |
| title required | string |
| starts_on | string or null <date> |
| published_by required | string (PublishedBy) Enum: "coach" "ai_coach"
|
required | Array of objects (MealPlanItem) Ordered by |
{- "id": "0190f5ca-1a2b-7c3d-8e4f-5a6b7c8d9e01",
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "version": 1,
- "status": "active",
- "title": "Week 5 meal plan",
- "starts_on": "2026-10-26",
- "published_by": "coach",
- "items": [
- {
- "id": "3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01",
- "day_index": 0,
- "meal_type": "breakfast",
- "sort_order": 0,
- "title": "Greek yogurt bowl",
- "description": "Nonfat Greek yogurt with berries and almonds",
- "protein_g": 24,
- "calories_kcal": 310,
- "details": {
- "prep_minutes": 5
}, - "recipe_id": null,
- "swap": null
}, - {
- "id": "4d2f9a31-6c8b-4e0d-9f72-1b3c5d7e9f02",
- "day_index": 0,
- "meal_type": "dinner",
- "sort_order": 0,
- "title": "Chicken and quinoa bowl",
- "description": "Grilled chicken, quinoa, roasted vegetables",
- "protein_g": 38,
- "calories_kcal": 480,
- "details": {
- "prep_minutes": 25
}, - "recipe_id": null,
- "swap": {
- "original_title": "Salmon with quinoa",
- "reason": "Does not like fish",
- "swapped_by": "ai",
- "swapped_at": "2026-10-26T11:02:00Z"
}
}
]
}The active workout plan, day by day. Error code workout_plan_not_found (404).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| protocol_id | string or null <uuid> |
| version required | integer |
| status required | string Enum: "active" "superseded" |
| title required | string |
| starts_on | string or null <date> |
| published_by required | string (PublishedBy) Enum: "coach" "ai_coach"
|
required | Array of objects (WorkoutPlanItem) |
{- "id": "0190f5cb-2b3c-7d4e-8f50-6a7b8c9d0e12",
- "protocol_id": "0190f5c6-9cad-7e12-8f34-5a6b7c8d9e01",
- "version": 1,
- "status": "active",
- "title": "Muscle-preserving strength, weeks 5-8",
- "starts_on": "2026-10-26",
- "published_by": "coach",
- "items": [
- {
- "id": "1b3d5f7a-9c2e-4a6b-8d0f-2e4a6c8e0b2d",
- "day_index": 1,
- "sort_order": 0,
- "title": "Full-body strength A",
- "description": "Three moves, three sets each. Stop two reps before failure.",
- "duration_min": 30,
- "exercises": [
- {
- "exercise_id": "5a7c9e1b-3d5f-4a8c-9e2b-4d6f8a0c2e4f",
- "name": "Goblet squat",
- "sets": 3,
- "reps": "8-12",
- "rest_s": 60,
- "notes": "Keep your chest up",
}
]
}
]
}Rates a meal from 1 to 5 stars with an optional comment, shown on the plan as my_rating; rating again replaces it. Coaches see
it, and new plans, swaps and the AI Coach avoid meals rated 1 or 2. To rate the recipe itself wherever it
appears, use PUT /v1/patient/library/{library_item_id}/rating.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| item_id required | string <uuid> Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01 |
| rating required | integer [ 1 .. 5 ] |
| comment | string or null <= 500 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| rating required | integer [ 1 .. 5 ] |
| comment | string or null |
| updated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "rating": 4,
- "comment": "Filling, but I'd like it less spicy."
}{- "rating": 4,
- "comment": "Filling, but I'd like it less spicy.",
- "updated_at": "2026-10-27T13:05:00Z"
}Removes the patient's rating of the meal. Clearing a rating that isn't there changes nothing.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| item_id required | string <uuid> Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01 |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}The AI Coach swaps a meal for one that stays within the protocol and TeraRx's clinical rules.
The AI Coach proposes replacements for one meal and keeps only those that stay within the protocol's targets and the TeraRx's clinical rules (allergies, foods to avoid, calorie floor). Takes a few seconds. Options expire after 30 minutes. Counts toward the plan's daily AI Coach cap.
Error codes: ai_paused (409) when the AI Coach is paused for the plan, daily_message_cap_reached (429),
ai_unavailable (503).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| item_id required | string <uuid> Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| reason | string <= 500 characters Why the patient wants a swap, in their words. The AI Coach uses it to choose options. |
| avoid | Array of strings <= 20 items |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| item_id required | string <uuid> (Uuid) |
required | Array of objects (MealSwapOption) Only options that keep the protocol's targets and the brand's constraints (allergies, foods to avoid,
calorie floor). Empty when none does; |
| note | string or null |
| expires_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "reason": "I don't like fish",
- "avoid": [
- "fish"
]
}{- "item_id": "4d2f9a31-6c8b-4e0d-9f72-1b3c5d7e9f02",
- "options": [
- {
- "option_id": "opt_1",
- "title": "Chicken and quinoa bowl",
- "description": "Grilled chicken, quinoa, roasted vegetables",
- "protein_g": 38,
- "calories_kcal": 480,
- "details": {
- "prep_minutes": 25
}
}, - {
- "option_id": "opt_2",
- "title": "Tofu stir-fry with edamame",
- "description": "Firm tofu, edamame, broccoli and brown rice",
- "protein_g": 34,
- "calories_kcal": 460,
- "details": {
- "prep_minutes": 20
}
}
], - "note": null,
- "expires_at": "2026-10-26T11:30:00Z"
}Replaces the meal with one of the options. The original meal and the reason are kept on the item and shown to
the care team. Error codes: swap_option_expired (409), swap_option_not_found (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| item_id required | string <uuid> Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| option_id required | string |
| reason | string <= 500 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| day_index required | integer >= 0 Day in the plan's cycle, starting at 0 on |
| meal_type required | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" |
| sort_order required | integer |
| title required | string |
| description | string or null |
| protein_g | number or null |
| calories_kcal | integer or null |
required | object (MealDetails) |
| recipe_id | string or null <uuid> A library recipe, when the meal comes from one. Read it with |
MealSwapRecord (object) or null | |
MealRating (object) or null The patient's rating of this meal, or null. |
{- "option_id": "opt_1",
- "reason": "I don't like fish"
}{- "id": "4d2f9a31-6c8b-4e0d-9f72-1b3c5d7e9f02",
- "day_index": 0,
- "meal_type": "dinner",
- "sort_order": 0,
- "title": "Chicken and quinoa bowl",
- "description": "Grilled chicken, quinoa, roasted vegetables",
- "protein_g": 38,
- "calories_kcal": 480,
- "details": {
- "prep_minutes": 25
}, - "recipe_id": null,
- "swap": {
- "original_title": "Salmon with quinoa",
- "reason": "I don't like fish",
- "swapped_by": "ai",
- "swapped_at": "2026-10-26T11:02:00Z"
}
}Searches the recipes and exercises the patient can see, alphabetically. Recipes that contain one of the patient's food allergies, or don't fit one of their dietary restrictions, are never returned. Food dislikes are not filtered.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| kind | string (LibraryKind) Enum: "recipe" "exercise" Example: kind=recipe Omit for both. |
| meal_type | string (MealType) Enum: "breakfast" "lunch" "dinner" "snack" Example: meal_type=breakfast Recipes for this meal. |
| tag | string^[a-z][a-z0-9_]*$ Example: tag=mild_nausea Items with this tag, for example |
| q | string <= 100 characters Example: q=yogurt Words in the title. |
| favorites | boolean Example: favorites=true Only the items the patient saved, most recently saved first. |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (LibraryItemSummary) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
- "kind": "recipe",
- "slug": "greek-yogurt-protein-bowl",
- "title": "Greek yogurt protein bowl",
- "category": "breakfast",
- "tags": [
- "high_protein",
- "mild_nausea"
], - "calories_kcal": 320,
- "protein_g": 31,
- "favorite": true
}
], - "next_cursor": null
}The full recipe or exercise, for example from a meal plan's recipe_id or a workout's exercise_id. Unlike the
list, this returns the item whatever the patient's profile; use recipe.allergens to warn them.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| library_item_id required | string <uuid> Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| kind required | string (LibraryKind) Enum: "recipe" "exercise" |
| slug | string or null |
| title required | string |
| summary | string or null |
| category | string or null A meal course for recipes, a body area for exercises. |
| tags required | Array of strings (LibraryTags) [ items^[a-z][a-z0-9_]*$ ] Labels for filtering, for example |
required | Array of objects (LibraryMedia) |
RecipeDetails (object) or null Set when | |
ExerciseDetails (object) or null Set when | |
| favorite required | boolean True when the patient saved it. |
required | MealRating (object) or null The patient's rating of a recipe, or null. |
{- "id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
- "kind": "recipe",
- "slug": "greek-yogurt-protein-bowl",
- "title": "Greek yogurt protein bowl",
- "summary": "A no-cook breakfast with 31 g of protein.",
- "category": "breakfast",
- "tags": [
- "high_protein",
- "mild_nausea"
], - "media": [
- {
- "kind": "image",
}
], - "recipe": {
- "servings": 1,
- "serving_size": "1 bowl (about 300 g)",
- "prep_minutes": 5,
- "cook_minutes": 0,
- "difficulty": "easy",
- "cuisine": null,
- "meal_types": [
- "breakfast",
- "snack"
], - "appliances": [ ],
- "ingredients": [
- {
- "name": "Greek yogurt, plain, nonfat",
- "quantity": 200,
- "unit": "g"
}, - {
- "name": "Blueberries",
- "quantity": 75,
- "unit": "g"
}, - {
- "name": "Chia seeds",
- "quantity": 10,
- "unit": "g"
}
], - "steps": [
- "Spoon the yogurt into a bowl",
- "Top with the blueberries and chia seeds"
], - "nutrients": {
- "calories_kcal": 320,
- "protein_g": 31,
- "carbs_g": 30,
- "fat_g": 9,
- "fiber_g": 6
}, - "allergens": [
- "milk"
], - "diets": [
- "vegetarian",
- "gluten_free"
], - "why": "Small, cold and high in protein, so it is easy to eat on a low-appetite day."
}, - "exercise": null,
- "favorite": true,
- "my_rating": {
- "rating": 5,
- "comment": null,
- "updated_at": "2026-10-24T08:15:00Z"
}
}Adds the recipe or exercise to the patient's favorites (GET /v1/patient/library?favorites=true). Saving it again changes nothing.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| library_item_id required | string <uuid> Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Removes the recipe or exercise from the patient's favorites. Removing one that isn't saved changes nothing.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| library_item_id required | string <uuid> Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Rates a recipe from 1 to 5 stars with an optional comment; rating again replaces it. New meal plans, swaps and the AI Coach avoid
recipes rated 1 or 2, and coaches see the ratings. After a low rating the app can offer to add a food dislike with
PATCH /v1/patient. Error code: not_a_recipe (422) for an exercise.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| library_item_id required | string <uuid> Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f |
| rating required | integer [ 1 .. 5 ] |
| comment | string or null <= 500 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| rating required | integer [ 1 .. 5 ] |
| comment | string or null |
| updated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "rating": 2,
- "comment": "Too sweet for me."
}{- "rating": 2,
- "comment": "Too sweet for me.",
- "updated_at": "2026-10-27T12:40:00Z"
}Removes the patient's rating of the recipe. Clearing a rating that isn't there changes nothing.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| library_item_id required | string <uuid> Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Threads with the coaches on the coached plan, photos, editing the last message, read state and reactions.
One thread per coach on the care team, with unread counts.
Plans: AI + health and fitness coaches (ai_health_fitness).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (CareConversation) One thread per coach on the care team. |
{- "data": [
- {
- "id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "coach": {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD",
- "NBC-HWC"
], - "assigned_at": "2026-10-21T15:10:00Z"
}, - "last_message_at": "2026-10-26T13:40:00Z",
- "unread_count": 1,
- "patient_last_read_at": "2026-10-26T09:00:00Z",
- "coach_last_read_at": "2026-10-26T13:40:00Z"
}
]
}Newest first. New messages are announced by message.created; fetch them here.
Plans: AI + health and fitness coaches (ai_health_fitness).
| conversation_id required | string <uuid> Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12 |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Message) Newest first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23",
- "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "sender": {
- "type": "coach",
- "display_name": "Maya C.",
- "care_role": "health_coach"
}, - "body": "Great job on your protein yesterday. How is the nausea today?",
- "created_at": "2026-10-26T13:40:00Z",
- "edited_at": null,
- "deleted": false,
- "seen_by_other_side": false,
- "attachment": null,
- "reactions": [ ],
- "feedback": null
}
], - "next_cursor": "eyJpZCI6IjAxOTBmNWM0In0"
}Text, a photo, or both. For a photo, start an upload with POST /v1/patient/message-photos, upload it, then
send its upload_id as photo_upload_id. Coaching is not emergency care. Show the 911 and 988 guidance next
to the message box: coaches do not answer around the clock. Error codes: conversation_archived (409),
upload_not_found (422).
Plans: AI + health and fitness coaches (ai_health_fitness).
| conversation_id required | string <uuid> Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| body required | string [ 1 .. 4000 ] characters |
| photo_upload_id | string <uuid> (Uuid) From |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| conversation_id required | string <uuid> (Uuid) |
required | object (MessageSender) |
| body required | string Empty when the message was deleted. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| edited_at | string or null <date-time> |
| deleted required | boolean |
| seen_by_other_side | boolean True when the other side has read up to this message. |
MessageAttachment (object) or null A protocol card, a notice in the thread, or a photo the patient sent their coach. | |
required | Array of objects (Reaction) |
MessageFeedback (object) or null The patient's rating, on the AI Coach's messages only. |
{- "body": "Nausea is better today. Can I swap dinner?"
}{- "id": "0190f5c4-3f40-7b52-8c67-8d9e0f1a2b34",
- "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "sender": {
- "type": "patient",
- "display_name": "Jordan",
- "care_role": null
}, - "body": "Nausea is better today. Can I swap dinner?",
- "created_at": "2026-10-26T14:05:00Z",
- "edited_at": null,
- "deleted": false,
- "seen_by_other_side": false,
- "attachment": null,
- "reactions": [ ],
- "feedback": null
}Moves the patient's read marker. The coach sees their messages marked as read.
Plans: AI + health and fitness coaches (ai_health_fitness).
| conversation_id required | string <uuid> Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12 |
| read_at | string <date-time> (Timestamp) Defaults to now. Everything sent up to this time counts as read. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| conversation_id required | string <uuid> (Uuid) |
| patient_last_read_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| unread_count required | integer |
{- "read_at": "2026-10-26T14:05:00Z"
}{- "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "patient_last_read_at": "2026-10-26T14:05:00Z",
- "unread_count": 0
}Changes the text of the patient's most recent message in a care thread. Only that one can be edited, and only in
care threads (AI Coach replies are written from what the patient sent). The coach sees it marked as edited, and
the previous text is kept in the record's history. Error codes: not_last_message (409) for an older message
or one the patient didn't send, message_not_editable (422) in an AI Coach chat, conversation_archived (409).
Plans: AI + health and fitness coaches (ai_health_fitness).
| message_id required | string <uuid> Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23 |
| body required | string [ 1 .. 4000 ] characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| conversation_id required | string <uuid> (Uuid) |
required | object (MessageSender) |
| body required | string Empty when the message was deleted. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| edited_at | string or null <date-time> |
| deleted required | boolean |
| seen_by_other_side | boolean True when the other side has read up to this message. |
MessageAttachment (object) or null A protocol card, a notice in the thread, or a photo the patient sent their coach. | |
required | Array of objects (Reaction) |
MessageFeedback (object) or null The patient's rating, on the AI Coach's messages only. |
{- "body": "Nausea is better today. Can I swap tonight's dinner?"
}{- "id": "0190f5c4-3f40-7b52-8c67-8d9e0f1a2b34",
- "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "sender": {
- "type": "patient",
- "display_name": "Jordan",
- "care_role": null
}, - "body": "Nausea is better today. Can I swap tonight's dinner?",
- "created_at": "2026-10-26T14:05:00Z",
- "edited_at": "2026-10-26T14:06:30Z",
- "deleted": false,
- "seen_by_other_side": false,
- "attachment": null,
- "reactions": [ ],
- "feedback": null
}Replaces the patient's reactions on any message in their care or AI Coach threads. Send [] to remove them.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| message_id required | string <uuid> Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23 |
| emojis required | Array of strings <= 10 items unique [ items [ 1 .. 16 ] characters ] The patient's full set of reactions on the message. An empty list removes them all. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| message_id required | string <uuid> (Uuid) |
required | Array of objects (Reaction) |
{- "emojis": [
- "👍"
]
}{- "message_id": "0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23",
- "reactions": [
- {
- "emoji": "👍",
- "by": "patient"
}
]
}Returns a pre-signed URL. Upload the photo there, then send upload_id as photo_upload_id with the message.
An upload not sent within 1 hour is deleted. The coach sees the photo in the thread; the patient's copy comes
back as an image attachment with a short-lived url.
Plans: AI + health and fitness coaches (ai_health_fitness).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| content_type required | string Enum: "image/jpeg" "image/png" "image/heic" |
| content_length required | integer [ 1 .. 10485760 ] Bytes, at most 10 MB. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| upload_id required | string <uuid> (Uuid) |
| upload_url required | string <uri> Pre-signed URL. Upload the bytes with |
| method required | string Value: "PUT" |
required | object Headers to send with the upload, exactly as given. |
| expires_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "content_type": "image/jpeg",
- "content_length": 2097152
}{- "upload_id": "9f3d5b71-2a4c-4e6f-8b0d-1c3e5a7b9d2f",
- "method": "PUT",
- "headers": {
- "Content-Type": "image/jpeg"
}, - "expires_at": "2026-10-27T15:15:00Z"
}Popups that ask the patient about their coach. TeraRx writes the questions and decides when to ask.
Popups waiting for an answer, oldest first. care_team.feedback_due tells your server when one opens; check here
too, for example when the app opens, in case a webhook was missed.
Plans: AI + health and fitness coaches (ai_health_fitness).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (FeedbackRequest) Popups waiting for an answer, oldest first. |
{- "data": [
- {
- "id": "0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6",
- "status": "requested",
- "care_role": "health_coach",
- "coach": {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD"
], - "assigned_at": "2026-09-15T10:00:00Z"
}, - "survey": {
- "code": "coach_feedback",
- "version": 1,
- "title": "How is coaching going?",
- "intro": "It takes less than a minute.",
- "questions": [
- {
- "key": "overall",
- "kind": "rating",
- "label": "How would you rate your coach so far?",
- "required": true,
- "scale_max": 5
}, - {
- "key": "comment",
- "kind": "text",
- "label": "Anything else you would like to share?",
- "required": false,
- "max_length": 1000
}
]
}, - "requested_at": "2026-10-27T15:00:00Z",
- "expires_at": "2026-11-10T15:00:00Z",
- "answers": null,
- "answered_at": null
}
]
}The popup's title, questions and the coach it is about. TeraRx writes the questions; render each one by its
kind. Don't show a popup whose status is no longer requested.
Plans: AI + health and fitness coaches (ai_health_fitness).
| feedback_request_id required | string <uuid> Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| status required | string Enum: "requested" "answered" "dismissed" "expired" |
| care_role required | string (CareRole) ^[a-z][a-z0-9_]*$ A care role on the coached plan, |
required | object (CareTeamMember) The coach the feedback is about, as the patient sees them. |
required | object (FeedbackSurvey) |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| expires_at | string or null <date-time> After this the popup can't be answered; don't show it. |
FeedbackAnswers (object) or null Set once answered. | |
| answered_at | string or null <date-time> |
{- "id": "0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6",
- "status": "requested",
- "care_role": "health_coach",
- "coach": {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD"
], - "assigned_at": "2026-09-15T10:00:00Z"
}, - "survey": {
- "code": "coach_feedback",
- "version": 1,
- "title": "How is coaching going?",
- "intro": "It takes less than a minute.",
- "questions": [
- {
- "key": "overall",
- "kind": "rating",
- "label": "How would you rate your coach so far?",
- "required": true,
- "scale_max": 5
}, - {
- "key": "helpful",
- "kind": "thumbs",
- "label": "Did your coach help you this month?",
- "required": false
}, - {
- "key": "more_help_with",
- "kind": "choice",
- "label": "What would you like more help with?",
- "required": false,
- "options": [
- {
- "value": "nutrition",
- "label": "Nutrition"
}, - {
- "value": "exercise",
- "label": "Exercise"
}, - {
- "value": "side_effects",
- "label": "Side effects"
}, - {
- "value": "motivation",
- "label": "Motivation"
}
]
}, - {
- "key": "comment",
- "kind": "text",
- "label": "Anything else you would like to share?",
- "required": false,
- "max_length": 1000
}
]
}, - "requested_at": "2026-10-27T15:00:00Z",
- "expires_at": "2026-11-10T15:00:00Z",
- "answers": null,
- "answered_at": null
}Checks each answer against its question: every required question answered, ratings from 1 to scale_max,
thumbs up or down, choices among the options, text within max_length. The answers go to TeraRx, which
uses them to support its coaches. Error codes: validation_failed (422, with a pointer per answer such as
/answers/overall), feedback_request_closed (409) once answered, dismissed or expired.
Plans: AI + health and fitness coaches (ai_health_fitness).
| feedback_request_id required | string <uuid> Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
required | object (FeedbackAnswers) One value per question |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| status required | string Enum: "requested" "answered" "dismissed" "expired" |
| care_role required | string (CareRole) ^[a-z][a-z0-9_]*$ A care role on the coached plan, |
required | object (CareTeamMember) The coach the feedback is about, as the patient sees them. |
required | object (FeedbackSurvey) |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| expires_at | string or null <date-time> After this the popup can't be answered; don't show it. |
FeedbackAnswers (object) or null Set once answered. | |
| answered_at | string or null <date-time> |
{- "answers": {
- "overall": 5,
- "helpful": "up",
- "more_help_with": "side_effects",
- "comment": "Maya's tips for nausea made a real difference."
}
}{- "id": "0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6",
- "status": "answered",
- "care_role": "health_coach",
- "coach": {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD"
], - "assigned_at": "2026-09-15T10:00:00Z"
}, - "survey": {
- "code": "coach_feedback",
- "version": 1,
- "title": "How is coaching going?",
- "intro": "It takes less than a minute.",
- "questions": [
- {
- "key": "overall",
- "kind": "rating",
- "label": "How would you rate your coach so far?",
- "required": true,
- "scale_max": 5
}
]
}, - "requested_at": "2026-10-27T15:00:00Z",
- "expires_at": "2026-11-10T15:00:00Z",
- "answers": {
- "overall": 5,
- "helpful": "up",
- "more_help_with": "side_effects",
- "comment": "Maya's tips for nausea made a real difference."
}, - "answered_at": "2026-10-27T15:02:00Z"
}The patient closed the popup without answering; it won't be offered again. Error code:
feedback_request_closed (409) once answered, dismissed or expired.
Plans: AI + health and fitness coaches (ai_health_fitness).
| feedback_request_id required | string <uuid> Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| status required | string Enum: "requested" "answered" "dismissed" "expired" |
| care_role required | string (CareRole) ^[a-z][a-z0-9_]*$ A care role on the coached plan, |
required | object (CareTeamMember) The coach the feedback is about, as the patient sees them. |
required | object (FeedbackSurvey) |
| requested_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| expires_at | string or null <date-time> After this the popup can't be answered; don't show it. |
FeedbackAnswers (object) or null Set once answered. | |
| answered_at | string or null <date-time> |
{- "id": "0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6",
- "status": "dismissed",
- "care_role": "health_coach",
- "coach": {
- "care_role": "health_coach",
- "display_name": "Maya C.",
- "title": "Health Coach",
- "credentials": [
- "RD"
], - "assigned_at": "2026-09-15T10:00:00Z"
}, - "survey": {
- "code": "coach_feedback",
- "version": 1,
- "title": "How is coaching going?",
- "intro": "It takes less than a minute.",
- "questions": [
- {
- "key": "overall",
- "kind": "rating",
- "label": "How would you rate your coach so far?",
- "required": true,
- "scale_max": 5
}
]
}, - "requested_at": "2026-10-27T15:00:00Z",
- "expires_at": "2026-11-10T15:00:00Z",
- "answers": null,
- "answered_at": null
}Chat with the AI Coach on every plan, and what it remembers. Replies stream or can be polled.
The patient's chats with the AI Coach, most recent first. A patient can have many.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (AiCoachConversation) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
- "title": "Dinner ideas",
- "created_at": "2026-10-26T11:00:00Z",
- "last_message_at": "2026-10-26T11:01:30Z"
}
], - "next_cursor": null
}Opens a new chat. The AI Coach remembers confirmed preferences across chats.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| title | string <= 120 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| title | string or null |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| last_message_at | string or null <date-time> |
{- "title": "Dinner ideas"
}{- "id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
- "title": "Dinner ideas",
- "created_at": "2026-10-26T11:00:00Z",
- "last_message_at": null
}The patient's messages and the AI Coach's replies, newest first.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| conversation_id required | string <uuid> Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12 |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Message) Newest first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5cc-5e6f-7071-9283-9c0d1e2f3a45",
- "conversation_id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
- "sender": {
- "type": "ai",
- "display_name": "AI Coach",
- "care_role": null
}, - "body": "Try a chicken and quinoa bowl tonight. It keeps you near 38 g of protein.",
- "created_at": "2026-10-26T11:01:30Z",
- "edited_at": null,
- "deleted": false,
- "seen_by_other_side": true,
- "attachment": null,
- "reactions": [ ],
- "feedback": {
- "rating": "up",
- "note": null
}
}
], - "next_cursor": null
}Stores the patient's message and starts the AI Coach's reply. The response has the stored message and the reply in
pending; get the reply with GET /v1/patient/ai-coach/messages/{message_id}/reply, by polling or as a stream.
The AI Coach is not emergency care. Show the 911 and 988 guidance next to the message box. When the AI Coach escalates (a red-flag
symptom, a dosing or medication question, a new or severe side effect), the reply carries escalation:
on ai_only it is a clinical flag to the brand's prescriber, on the coached plan a task for the coach.
When the AI Coach is paused for the plan, the reply is the plan's holding message. Error codes:
daily_message_cap_reached (429), ai_unavailable (503).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| conversation_id required | string <uuid> Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12 |
| Idempotency-Key required | string [ 8 .. 255 ] characters Example: 7d0c7f4e-1a8b-4c55-9d3e-2f6a1b0c9e84 A unique key per logical request, for example a UUID. Retrying with the same key and the same body
within 24 hours returns the first response (with |
| body required | string [ 1 .. 4000 ] characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
required | object (Message) |
required | object (AiCoachReply) |
{- "body": "What can I eat tonight instead of salmon?"
}{- "message": {
- "id": "0190f5cc-4d5e-7f60-8172-8b9c0d1e2f34",
- "conversation_id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
- "sender": {
- "type": "patient",
- "display_name": "Jordan",
- "care_role": null
}, - "body": "What can I eat tonight instead of salmon?",
- "created_at": "2026-10-26T11:01:00Z",
- "edited_at": null,
- "deleted": false,
- "seen_by_other_side": false,
- "attachment": null,
- "reactions": [ ],
- "feedback": null
}, - "reply": {
- "reply_to_message_id": "0190f5cc-4d5e-7f60-8172-8b9c0d1e2f34",
- "status": "pending",
- "message": null,
- "escalation": null,
- "show_emergency_guidance": false
}
}message_id is the patient's message. Two ways to read the reply:
Accept: application/json every 1-2 seconds until status is completed or failed.Accept: text/event-stream (Server-Sent Events). Events: delta ({"text": "..."}, the
next piece of the reply), then one completed with the full AiCoachReply, or error with a problem document.
Guardrails run before text is streamed, so a blocked reply never streams.Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| message_id required | string <uuid> Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| reply_to_message_id required | string <uuid> (Uuid) |
| status required | string Enum: "pending" "running" "completed" "failed" |
Message (object) or null The AI Coach's reply, once | |
AiCoachEscalation (object) or null | |
| show_emergency_guidance required | boolean When true, show the 911 and 988 guidance above the reply. |
{- "reply_to_message_id": "0190f5cc-4d5e-7f60-8172-8b9c0d1e2f34",
- "status": "completed",
- "message": {
- "id": "0190f5cc-5e6f-7071-9283-9c0d1e2f3a45",
- "conversation_id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
- "sender": {
- "type": "ai",
- "display_name": "AI Coach",
- "care_role": null
}, - "body": "Try a chicken and quinoa bowl tonight. It keeps you near 38 g of protein.",
- "created_at": "2026-10-26T11:01:30Z",
- "edited_at": null,
- "deleted": false,
- "seen_by_other_side": false,
- "attachment": null,
- "reactions": [ ],
- "feedback": null
}, - "escalation": null,
- "show_emergency_guidance": false
}Thumbs up or down on one of the AI Coach's messages, with an optional note. Rating again replaces it. Error code not_an_ai_message (422).
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| message_id required | string <uuid> Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23 |
| rating required | string Enum: "up" "down" |
| note | string <= 1000 characters |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| message_id required | string <uuid> (Uuid) |
| rating required | string Enum: "up" "down" |
| note | string or null |
{- "rating": "down",
- "note": "The swap still had fish in it"
}{- "message_id": "0190f5cc-5e6f-7071-9283-9c0d1e2f3a45",
- "rating": "down",
- "note": "The swap still had fish in it"
}Things the AI Coach remembers from the patient's chats, such as foods they dislike or when they like to work out, so the patient can check them. Brands never see them.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (AiCoachMemory) Newest first. |
{- "data": [
- {
- "id": "6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b",
- "summary": "Doesn't like fish",
- "confirmed": false,
- "created_at": "2026-10-24T18:20:00Z",
- "updated_at": "2026-10-24T18:20:00Z"
}, - {
- "id": "7d9f1b3c-5e7a-4b2d-9f4c-6e8a0b2d4f6c",
- "summary": "Prefers to work out before breakfast",
- "confirmed": true,
- "created_at": "2026-10-22T07:05:00Z",
- "updated_at": "2026-10-23T08:00:00Z"
}
]
}The patient confirms that a memory is right (or takes the confirmation back). To correct a memory, delete it and tell the AI Coach.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| memory_id required | string <uuid> Example: 6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b |
| confirmed required | boolean |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| summary required | string What the AI Coach remembers, in one line. |
| confirmed required | boolean The patient confirmed it is right. |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "confirmed": true
}{- "id": "6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b",
- "summary": "Doesn't like fish",
- "confirmed": true,
- "created_at": "2026-10-24T18:20:00Z",
- "updated_at": "2026-10-26T10:00:00Z"
}Deletes the memory, and the AI Coach stops using it at once. Deleting it again returns 204.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| memory_id required | string <uuid> Example: 6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b |
{- "type": "about:blank",
- "title": "Unauthorized",
- "status": 401,
- "detail": "The access token has expired. Request a new one.",
- "code": "token_expired",
- "request_id": "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"
}Upcoming injection, check-in, logging and habit reminders, and the patient's reminder settings, including whether the AI Coach may start conversations.
Injection days, check-in forms, logging nudges and habits. Brand apps also receive reminder.due and send the
notification in their own words; this list is for showing what is coming.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| status | string Default: "scheduled" Enum: "scheduled" "sent" "cancelled" Example: status=scheduled |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (Reminder) Soonest first. |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45",
- "kind": "injection",
- "ref_key": null,
- "due_at": "2026-11-02T13:00:00Z",
- "status": "scheduled",
- "recurring": true
}
], - "next_cursor": null
}Moves a scheduled or just-sent reminder later by minutes (5 minutes to 24 hours). Error code reminder_not_snoozable (409) once cancelled.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| reminder_id required | string <uuid> Example: 0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45 |
| minutes required | integer [ 5 .. 1440 ] |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| kind required | string Enum: "injection" "check_in" "log_nudge" "habit" |
| ref_key | string or null The habit key or form assignment id the reminder is about. |
| due_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| status required | string Enum: "scheduled" "sent" "cancelled" |
| recurring | boolean True when the next occurrence is scheduled after this one is sent. |
{- "minutes": 60
}{- "id": "0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45",
- "kind": "injection",
- "ref_key": null,
- "due_at": "2026-11-02T14:00:00Z",
- "status": "scheduled",
- "recurring": true
}Which reminders are on, when they are sent, and quiet hours. Anything the patient has not set shows the brand's default.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object (ReminderKindSetting) The day comes from the prescriber's injection schedule; the patient sets only the time. |
required | object (CheckInReminderSetting) |
required | object (ReminderKindSetting) |
required | object (ReminderKindSetting) |
required | object (AiCoachOutreachSetting) |
required | QuietHours (object) or null |
{- "injection": {
- "enabled": true,
- "time": "09:00"
}, - "check_in": {
- "enabled": true
}, - "log_nudge": {
- "enabled": true,
- "time": "19:30"
}, - "habit": {
- "enabled": true,
- "time": "08:00"
}, - "ai_coach_outreach": {
- "enabled": true
}, - "quiet_hours": {
- "start": "21:30",
- "end": "07:30"
}
}Turning a kind off cancels its scheduled reminders and stops reminder.due for it, so a brand app sends fewer
notifications too; turning it back on schedules the next one. A new time applies from the next reminder. The
settings belong to the patient and carry over to a later enrollment with the same brand.
Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).
object (ReminderKindSetting) | |
object (CheckInReminderSetting) | |
object (ReminderKindSetting) | |
object (ReminderKindSetting) | |
object (AiCoachOutreachSetting) | |
QuietHours (object) or null |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | object (ReminderKindSetting) The day comes from the prescriber's injection schedule; the patient sets only the time. |
required | object (CheckInReminderSetting) |
required | object (ReminderKindSetting) |
required | object (ReminderKindSetting) |
required | object (AiCoachOutreachSetting) |
required | QuietHours (object) or null |
{- "habit": {
- "enabled": false
}, - "quiet_hours": {
- "start": "21:30",
- "end": "07:30"
}
}{- "injection": {
- "enabled": true,
- "time": "09:00"
}, - "check_in": {
- "enabled": true
}, - "log_nudge": {
- "enabled": true,
- "time": "19:30"
}, - "habit": {
- "enabled": false,
- "time": "08:00"
}, - "ai_coach_outreach": {
- "enabled": true
}, - "quiet_hours": {
- "start": "21:30",
- "end": "07:30"
}
}Flags for the brand's prescriber, read and acted on with the clinical client. On the AI-only plan every
escalation lands here. Each write names the individual prescriber in on_behalf_of.
Your brand's flags, oldest first so the longest-waiting come first. By default only those needing action
(open and acknowledged). On the AI-only plan every escalation lands here, so poll this as a backstop to
the clinical.flag_raised webhook.
| status | Array of strings (ClinicalFlagStatus) Default: "open,acknowledged" Items Enum: "open" "acknowledged" "resolved" "dismissed" Example: status=open One or more statuses, comma-separated. |
| severity | string (Severity) Enum: "info" "warning" "critical" Example: severity=critical |
| enrollment_id | string <uuid> Example: enrollment_id=0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5 |
| external_patient_id | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ Example: external_patient_id=brand-pt-48213 Your own identifier, printable ASCII without spaces. |
| updated_since | string <date-time> Example: updated_since=2026-10-26T00:00:00Z Only flags changed after this time. |
| limit | integer [ 1 .. 100 ] Default: 25 Example: limit=25 Page size. |
| cursor | string <= 512 characters Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0 The |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
required | Array of objects (ClinicalFlag) |
| next_cursor required | string or null (NextCursor) Pass it back as |
{- "data": [
- {
- "id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "status": "open",
- "severity": "critical",
- "title": "Acute GI distress",
- "rule_code": "acute_gi",
- "source": "alert_rule",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "plan_code": "ai_only",
- "risk_level": "high",
- "raised_risk_to": "high",
- "details": {
- "symptom": "vomiting",
- "severity_score": 4,
- "escalation_reason": null,
- "values": { }
}, - "triggering_records": [
- {
- "type": "symptom_log",
- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T13:50:00Z"
}
], - "timeline": [
- {
- "occurred_at": "2026-10-26T08:00:00Z",
- "kind": "injection",
- "dose_mg": 0.5
}, - {
- "occurred_at": "2026-10-26T13:50:00Z",
- "kind": "symptom",
- "symptom": "vomiting",
- "severity": 4,
- "text": "Could not keep water down this morning"
}
], - "treatment": {
- "medication": {
- "ingredient": "semaglutide",
- "product_name": "Wegovy",
- "source": "branded",
- "route": "injection"
}, - "dose_mg": 0.5,
- "dose_amount": null,
- "dose_unit": null,
- "phase": "titration",
- "injection_weekday": 1,
- "started_on": "2026-09-28",
- "treatment_week": 5,
- "effective_from": "2026-10-26T00:00:00Z"
}, - "coach_note": null,
- "ack_due_at": "2026-10-26T16:05:00Z",
- "redelivery_count": 0,
- "acknowledgement": null,
- "resolution": null,
- "created_at": "2026-10-26T14:05:00Z",
- "updated_at": "2026-10-26T14:05:00Z"
}
], - "next_cursor": null
}The flag's symptom, severity, triggering records, a timeline around them, the treatment and any coach note.
| flag_id required | string <uuid> Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6 |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| id required | string <uuid> (Uuid) |
| status required | string (ClinicalFlagStatus) Enum: "open" "acknowledged" "resolved" "dismissed"
|
| severity required | string (Severity) Enum: "info" "warning" "critical" |
| title required | string |
| rule_code | string or null The alert rule that raised it, for example |
| source required | string Enum: "alert_rule" "ai_coach_escalation" "coach" An alert rule on the logs, the AI Coach escalating a conversation ( |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ The brand's patient id, to find the patient in your own system. |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" The enrollment's risk level now. |
| raised_risk_to | string or null Enum: "normal" "elevated" "high" null The level this flag raised the enrollment to. |
required | object What triggered the flag. |
required | Array of objects (TriggeringRecord) |
required | Array of objects (TimelineEntry) |
PatientTreatment (object) or null The treatment when the flag was raised. | |
| coach_note | string or null The coach's note, on the coached plan. |
| ack_due_at | string or null <date-time> Acknowledge before this time, set from the brand's agreed acknowledgement time. If it passes, |
| redelivery_count required | integer How many times the flag has been re-delivered for a missed |
AcknowledgementRecord (object) or null | |
ResolutionRecord (object) or null | |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "status": "open",
- "severity": "critical",
- "title": "Dosing question escalated by the AI Coach",
- "rule_code": null,
- "source": "ai_coach_escalation",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "plan_code": "ai_only",
- "risk_level": "elevated",
- "raised_risk_to": "elevated",
- "details": {
- "symptom": null,
- "severity_score": null,
- "escalation_reason": "dosing_question",
- "values": { }
}, - "triggering_records": [
- {
- "type": "message",
- "id": "0190f5cc-6f70-7182-a394-0d1e2f3a4b56",
- "occurred_at": "2026-10-26T11:05:00Z"
}
], - "timeline": [
- {
- "occurred_at": "2026-10-26T11:05:00Z",
- "kind": "ai_coach_escalation",
- "text": "Patient asked whether to take a second dose this week after missing Monday."
}
], - "treatment": null,
- "coach_note": null,
- "ack_due_at": "2026-10-26T13:05:00Z",
- "redelivery_count": 1,
- "acknowledgement": null,
- "resolution": null,
- "created_at": "2026-10-26T11:05:10Z",
- "updated_at": "2026-10-26T13:05:00Z"
}Tells TeraRx a prescriber has the flag, which stops re-delivery. It does not change risk.
Acknowledging again returns the flag unchanged. Fires clinical.flag_acknowledged.
Error codes: flag_closed (409) once resolved or dismissed, validation_failed (422) without name and npi or prescriber_id.
| flag_id required | string <uuid> Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
required | Prescriber (object) or Prescriber (object) (Prescriber) The individual prescriber acting through a clinical client. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| status required | string (ClinicalFlagStatus) Enum: "open" "acknowledged" "resolved" "dismissed"
|
| severity required | string (Severity) Enum: "info" "warning" "critical" |
| title required | string |
| rule_code | string or null The alert rule that raised it, for example |
| source required | string Enum: "alert_rule" "ai_coach_escalation" "coach" An alert rule on the logs, the AI Coach escalating a conversation ( |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ The brand's patient id, to find the patient in your own system. |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" The enrollment's risk level now. |
| raised_risk_to | string or null Enum: "normal" "elevated" "high" null The level this flag raised the enrollment to. |
required | object What triggered the flag. |
required | Array of objects (TriggeringRecord) |
required | Array of objects (TimelineEntry) |
PatientTreatment (object) or null The treatment when the flag was raised. | |
| coach_note | string or null The coach's note, on the coached plan. |
| ack_due_at | string or null <date-time> Acknowledge before this time, set from the brand's agreed acknowledgement time. If it passes, |
| redelivery_count required | integer How many times the flag has been re-delivered for a missed |
AcknowledgementRecord (object) or null | |
ResolutionRecord (object) or null | |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "on_behalf_of": {
- "npi": "1234567893",
- "name": "Dr. Alex Morgan, NP"
}
}{- "id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "status": "acknowledged",
- "severity": "critical",
- "title": "Acute GI distress",
- "rule_code": "acute_gi",
- "source": "alert_rule",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "plan_code": "ai_only",
- "risk_level": "high",
- "raised_risk_to": "high",
- "details": {
- "symptom": "vomiting",
- "severity_score": 4,
- "escalation_reason": null,
- "values": { }
}, - "triggering_records": [
- {
- "type": "symptom_log",
- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T13:50:00Z"
}
], - "timeline": [ ],
- "treatment": null,
- "coach_note": null,
- "ack_due_at": "2026-10-26T16:05:00Z",
- "redelivery_count": 0,
- "acknowledgement": {
- "at": "2026-10-26T14:20:00Z",
- "by": {
- "npi": "1234567893",
- "name": "Dr. Alex Morgan, NP"
}
}, - "resolution": null,
- "created_at": "2026-10-26T14:05:00Z",
- "updated_at": "2026-10-26T14:20:00Z"
}Records the prescriber's decision and sets the enrollment's risk level to new_risk_level. This is the only
way risk goes down. Resolving an open flag acknowledges it too. Fires clinical.flag_resolved.
Error codes: flag_closed (409) once resolved or dismissed, validation_failed (422).
| flag_id required | string <uuid> Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6 |
| Idempotency-Key | string [ 8 .. 255 ] characters Example: 3b9e5a10-6c2f-4d7e-8a41-9f0e2d1c7b36 Optional on this action. When sent, it works as on creating requests (24 hours, same body). |
required | Prescriber (object) or Prescriber (object) (Prescriber) The individual prescriber acting through a clinical client. |
| resolution_note required | string [ 1 .. 4000 ] characters What the prescriber decided. Shown to the care team; never sent in webhooks. |
| new_risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" The enrollment's risk level from now on. This is the only way risk goes down. |
| X-Request-Id | string Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b" Unique id of this request. Echoes a valid |
| RateLimit-Limit | integer Example: "600" Requests allowed per minute for this client (600 by default, set per contract). |
| RateLimit-Remaining | integer Example: "587" Requests left in the current window. |
| RateLimit-Reset | integer Example: "42" Seconds until the window resets. |
| Idempotent-Replayed | boolean Example: "true"
|
| id required | string <uuid> (Uuid) |
| status required | string (ClinicalFlagStatus) Enum: "open" "acknowledged" "resolved" "dismissed"
|
| severity required | string (Severity) Enum: "info" "warning" "critical" |
| title required | string |
| rule_code | string or null The alert rule that raised it, for example |
| source required | string Enum: "alert_rule" "ai_coach_escalation" "coach" An alert rule on the logs, the AI Coach escalating a conversation ( |
| enrollment_id required | string <uuid> (Uuid) |
| external_patient_id required | string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$ The brand's patient id, to find the patient in your own system. |
| plan_code required | string (PlanCode) Enum: "ai_only" "ai_health_fitness"
|
| risk_level required | string (RiskLevel) Enum: "normal" "elevated" "high" The enrollment's risk level now. |
| raised_risk_to | string or null Enum: "normal" "elevated" "high" null The level this flag raised the enrollment to. |
required | object What triggered the flag. |
required | Array of objects (TriggeringRecord) |
required | Array of objects (TimelineEntry) |
PatientTreatment (object) or null The treatment when the flag was raised. | |
| coach_note | string or null The coach's note, on the coached plan. |
| ack_due_at | string or null <date-time> Acknowledge before this time, set from the brand's agreed acknowledgement time. If it passes, |
| redelivery_count required | integer How many times the flag has been re-delivered for a missed |
AcknowledgementRecord (object) or null | |
ResolutionRecord (object) or null | |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| updated_at | string <date-time> (Timestamp) ISO 8601 in UTC. |
{- "on_behalf_of": {
- "npi": "1234567893",
- "name": "Dr. Alex Morgan, NP"
}, - "resolution_note": "Spoke with patient. Holding at 0.5 mg for two more weeks; antiemetic prescribed.",
- "new_risk_level": "elevated"
}{- "id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "status": "resolved",
- "severity": "critical",
- "title": "Acute GI distress",
- "rule_code": "acute_gi",
- "source": "alert_rule",
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "plan_code": "ai_only",
- "risk_level": "elevated",
- "raised_risk_to": "high",
- "details": {
- "symptom": "vomiting",
- "severity_score": 4,
- "escalation_reason": null,
- "values": { }
}, - "triggering_records": [
- {
- "type": "symptom_log",
- "id": "0190f5c9-7f80-7192-8cb4-3d4e5f607182",
- "occurred_at": "2026-10-26T13:50:00Z"
}
], - "timeline": [ ],
- "treatment": null,
- "coach_note": null,
- "ack_due_at": "2026-10-26T16:05:00Z",
- "redelivery_count": 0,
- "acknowledgement": {
- "at": "2026-10-26T14:20:00Z",
- "by": {
- "npi": "1234567893",
- "name": "Dr. Alex Morgan, NP"
}
}, - "resolution": {
- "at": "2026-10-26T15:10:00Z",
- "by": {
- "npi": "1234567893",
- "name": "Dr. Alex Morgan, NP"
}, - "resolution_note": "Spoke with patient. Holding at 0.5 mg for two more weeks; antiemetic prescribed.",
- "new_risk_level": "elevated"
}, - "created_at": "2026-10-26T14:05:00Z",
- "updated_at": "2026-10-26T15:10:00Z"
}TeraRx tells your server about changes with signed HTTPS POSTs to the webhook URL registered for your brand. A platform partner can register one URL and secret for all its brands; a brand's own URL, when it has one, takes precedence. Each delivery is one event. Payloads are thin: ids and the minimum needed to route (status, stage, severity, risk level), never message bodies, form answers, protocol text or measured values such as weights, doses and symptoms. Fetch details through the API with the right token.
{
"id": "0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7",
"type": "clinical.flag_raised",
"created_at": "2026-10-26T14:05:00Z",
"tenant_id": "a0000000-0000-4000-8000-000000000002",
"data": { "enrollment_id": "…", "external_patient_id": "brand-pt-48213", "…": "…" }
}
data always has enrollment_id and your external_patient_id. The full list of events is below, and each event's payload is in the Webhooks section of the API reference. You get every event by default; ask support@hellotera.com to limit the events sent to your webhook. GET /v1/events also takes a type filter.
| Event | When |
|---|---|
enrollment.created |
An enrollment was created |
enrollment.stage_changed |
The enrollment moved to the next stage |
enrollment.paused, enrollment.resumed, enrollment.cancelled |
Status changes |
care_team.assigned, care_team.changed |
A coach was assigned, reassigned or removed (coached plan) |
care_team.capacity_warning |
No licensed coach with capacity is available; the enrollment waits |
care_team.feedback_due |
The patient's feedback on a coach is due; show your feedback popup |
form.requested, onboarding.completed |
A form was sent; every blocking onboarding form is done |
protocol.published, protocol.updated |
A protocol went live, or a new version replaced it |
message.created |
A new care or AI Coach message (ids only) |
clinical.flag_raised |
A clinical flag for your prescriber |
clinical.flag_redelivered |
A flag passed ack_due_at without an acknowledgment |
clinical.flag_acknowledged, clinical.flag_resolved |
A prescriber acted on a flag |
patient.disengaged |
No logs or messages for your disengaged_after_days (7 by default) |
patient.milestone_reached |
The patient reached a milestone; read its details with the patient's token |
reminder.due |
A reminder is due; send the notification in your own words |
data_request.completed |
An export, delete or amend request finished |
On the AI-only plan, every alert that raises the risk level and every AI Coach escalation sends clinical.flag_raised, even when the alert rule is set not to notify the brand: there is no coach to reach, so your prescriber is the human in the loop.
Every delivery carries three headers:
| Header | Value |
|---|---|
TeraRx-Timestamp |
Unix time in seconds when this attempt was signed |
TeraRx-Signature |
v1=<hex>; during a secret rotation v1=<new>,v1=<old> |
TeraRx-Event-Id |
The event id, the same as id in the body |
Step by step:
TeraRx-Timestamp. Reject the delivery if it is more than 5 minutes from your clock (replay protection).<timestamp>.<raw body>.HMAC-SHA256(secret, signed_payload) as lowercase hex, using your webhook secret.TeraRx-Signature on commas. Accept the delivery if any v1= value matches, compared in constant time.TeraRx-Event-Id: a retry carries the same id. Store ids for at least 24 hours (30 days if you also replay with GET /v1/events) and answer 200 to a repeat without processing it again.2xx quickly, then process. Do the work after you respond, or queue it.With the secret example_webhook_secret_new, timestamp 1793023500 and this exact body:
{"id":"0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7","type":"clinical.flag_raised","created_at":"2026-10-26T14:05:00Z","tenant_id":"a0000000-0000-4000-8000-000000000002","data":{"enrollment_id":"0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5","external_patient_id":"brand-pt-48213","flag_id":"0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6","severity":"critical","raised_risk_to":"high","ack_due_at":"2026-10-26T16:05:00Z"}}
the header is TeraRx-Signature: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a. During a rotation where the old secret was example_webhook_secret_old, it is v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a,v1=2e8d962ded17a921cbc548b968abc024e9fcae75fd8ff8a0f2044999b0898416.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTeraRx(rawBody, headers, secret, now = Date.now()) {
const timestamp = headers["terarx-timestamp"];
if (!timestamp || Math.abs(now / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
return (headers["terarx-signature"] ?? "").split(",").some((part) => {
const [version, hex] = part.trim().split("=");
const given = Buffer.from(hex ?? "", "hex");
return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
});
}
import hashlib, hmac, time
# headers: a case-insensitive mapping, such as request.headers in Flask or Starlette.
def verify_terarx(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers.get("TeraRx-Timestamp", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(
part.strip().startswith("v1=") and hmac.compare_digest(part.strip()[3:], expected)
for part in headers.get("TeraRx-Signature", "").split(",")
)
Ask support@hellotera.com for a new secret. For the rotation window (24 hours by default) every delivery is signed with both secrets, new first. Deploy the new secret, confirm deliveries verify with it, and tell TeraRx to end the window; after that only the new signature is sent.
2xx within 10 seconds. Anything else, or no answer in time, is a failed attempt.GET /v1/events?since=<last event id you processed>, which keeps 30 days of events in order.created_at, or fetch the current state through the API, when order matters.A flag with an ack_due_at must be acknowledged by then; the time comes from the acknowledgment time agreed in your contract. When it passes, the flag stays open and TeraRx sends clinical.flag_redelivered with a new event id (so your dedupe does not swallow it), a new ack_due_at and a redelivery_count. This repeats until a prescriber acknowledges the flag with POST /v1/clinical-flags/{flag_id}/acknowledge. Route redeliveries to whoever covers when the first prescriber does not answer.
A new enrollment, after POST /v1/enrollments.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "enrollment.created" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (EnrollmentCreatedData) |
{- "id": "0190f5d0-1a2b-7c3d-8e4f-5a6b7c8d9e01",
- "type": "enrollment.created",
- "created_at": "2026-10-21T14:58:02Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "external_order_id": "ord-2026-10-000123",
- "plan_code": "ai_health_fitness",
- "status": "active",
- "stage": "created"
}
}The enrollment moved along created → awaiting_assignment → assigned → onboarding_pending → onboarding_completed → protocols_active.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "enrollment.stage_changed" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (EnrollmentStageChangedData) |
{- "id": "0190f5d0-2b3c-7d4e-8f50-6a7b8c9d0e12",
- "type": "enrollment.stage_changed",
- "created_at": "2026-10-22T09:30:01Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "from": "onboarding_pending",
- "to": "onboarding_completed"
}
}The enrollment was paused. Paused days are not billed.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "enrollment.paused" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object |
{- "id": "0190f5d0-3c4d-7e5f-8061-7b8c9d0e1f23",
- "type": "enrollment.paused",
- "created_at": "2026-11-01T10:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213"
}
}The enrollment is active again.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "enrollment.resumed" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object |
{- "id": "0190f5d0-4d5e-7f60-9172-8c9d0e1f2a34",
- "type": "enrollment.resumed",
- "created_at": "2026-11-15T08:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213"
}
}The enrollment ended. The retention period for the patient's records starts.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "enrollment.cancelled" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (EnrollmentCancelledData) |
{- "id": "0190f5d0-5e6f-7071-8283-9d0e1f2a3b45",
- "type": "enrollment.cancelled",
- "created_at": "2026-12-21T00:00:05Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "ended_at": "2026-12-21T00:00:00Z",
- "cancel_reason": "subscription_ended"
}
}A coach was assigned to a care role (coached plan). Coach identity is not sent.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "care_team.assigned" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (CareTeamAssignedData) |
{- "id": "0190f5d1-1a2b-7c3d-8e4f-5a6b7c8d9e02",
- "type": "care_team.assigned",
- "created_at": "2026-10-21T15:10:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "care_role": "health_coach",
- "assigned_at": "2026-10-21T15:10:00Z"
}
}A care role was reassigned to another coach, or ended (for example on a move to ai_only). reason_code says why,
for example state_change after you updated the patient's state.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "care_team.changed" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (CareTeamChangedData) |
{- "id": "0190f5d1-2b3c-7d4e-8f50-6a7b8c9d0e13",
- "type": "care_team.changed",
- "created_at": "2026-11-03T12:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "care_role": "fitness_coach",
- "change": "reassigned",
- "reason_code": "coach_unavailable",
- "changed_at": "2026-11-03T12:00:00Z"
}
}No coach with a license in the patient's state and free capacity is available, so the enrollment is waiting. Brands that use their own coaches never fall back to TeraRx coaches.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "care_team.capacity_warning" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (CareTeamCapacityWarningData) |
{- "id": "0190f5d1-3c4d-7e5f-9061-7b8c9d0e1f24",
- "type": "care_team.capacity_warning",
- "created_at": "2026-10-21T19:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "care_role": "fitness_coach",
- "state_code": "NY",
- "waiting_since": "2026-10-21T14:58:02Z"
}
}TeraRx would like the patient's feedback on the coach in care_role. Show your feedback popup: read its content with
GET /v1/patient/feedback-requests/{feedback_request_id} and send the answers with
POST /v1/patient/feedback-requests/{feedback_request_id}/response. TeraRx decides when to ask.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "care_team.feedback_due" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (CareTeamFeedbackDueData) |
{- "id": "0190f5e3-5d6e-7f80-8a91-c2d3e4f5a6b7",
- "type": "care_team.feedback_due",
- "created_at": "2026-10-27T15:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "feedback_request_id": "0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6",
- "care_role": "health_coach",
- "expires_at": "2026-11-10T15:00:00Z"
}
}A form was sent to the patient. Prompt them in your app; they fill it in with GET /v1/patient/forms/{form_assignment_id}.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "form.requested" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (FormRequestedData) |
{- "id": "0190f5d1-4d5e-7f60-8172-8c9d0e1f2a35",
- "type": "form.requested",
- "created_at": "2026-10-21T15:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "form_assignment_id": "0190f5c3-7e21-7a40-9b11-5d6e7f8091a2",
- "form_code": "glp1_onboarding",
- "purpose": "onboarding",
- "due_at": "2026-10-24T15:00:00Z"
}
}Every blocking onboarding form is done. Protocol drafting starts.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "onboarding.completed" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (OnboardingCompletedData) |
{- "id": "0190f5d1-5e6f-7071-9283-9d0e1f2a3b46",
- "type": "onboarding.completed",
- "created_at": "2026-10-22T09:30:01Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "completed_at": "2026-10-22T09:30:00Z"
}
}The first version of a protocol type is live. The patient reads it with GET /v1/patient/protocols/{protocol_type}.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "protocol.published" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ProtocolPublishedData) |
{- "id": "0190f5d1-6f70-7182-a394-0e1f2a3b4c57",
- "type": "protocol.published",
- "created_at": "2026-10-23T16:45:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "protocol_id": "0190f5c6-7a8b-7c90-8d12-3e4f5a6b7c8d",
- "protocol_type": "nutrition",
- "version": 1,
- "published_at": "2026-10-23T16:45:00Z"
}
}A new version replaced the active protocol of a type (for example after a dose change).
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "protocol.updated" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ProtocolPublishedData) |
{- "id": "0190f5d1-7081-7293-84a5-1f2a3b4c5d68",
- "type": "protocol.updated",
- "created_at": "2026-10-25T08:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "protocol_id": "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90",
- "protocol_type": "nutrition",
- "version": 2,
- "published_at": "2026-10-25T08:00:00Z"
}
}A new message in a care thread or from the AI Coach, so your app can notify the patient. Ids only: the body is never sent. Keep push notification text generic ("You have a new message").
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "message.created" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (MessageCreatedData) |
{- "id": "0190f5d1-8192-73a4-95b6-2a3b4c5d6e79",
- "type": "message.created",
- "created_at": "2026-10-26T13:40:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
- "message_id": "0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23",
- "conversation_kind": "care",
- "sender_type": "coach"
}
}A clinical flag for your prescriber. Read it with the clinical client (GET /v1/clinical-flags/{flag_id}) and
acknowledge it before ack_due_at.
Sent when an alert rule set to notify the brand fires, and on the AI-only plan for every alert that raises the risk level and every AI Coach escalation, whatever the rule says: there is no coach to reach. Emergencies are never handled by flag; the patient is told to call 911 or 988.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "clinical.flag_raised" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ClinicalFlagRaisedData) |
{- "id": "0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7",
- "type": "clinical.flag_raised",
- "created_at": "2026-10-26T14:05:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "severity": "critical",
- "raised_risk_to": "high",
- "ack_due_at": "2026-10-26T16:05:00Z"
}
}A flag passed its ack_due_at without an acknowledgement. It stays open, gets a new deadline and is sent again
with a new event id (so your dedupe does not drop it), until a prescriber acknowledges it. Escalate it within your organization.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "clinical.flag_redelivered" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ClinicalFlagRedeliveredData) |
{- "id": "0190f5e0-1b2c-7d3e-8f4a-5b6c7d8e9f01",
- "type": "clinical.flag_redelivered",
- "created_at": "2026-10-26T16:05:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "severity": "critical",
- "ack_due_at": "2026-10-26T18:05:00Z",
- "redelivery_count": 1,
- "first_raised_at": "2026-10-26T14:05:00Z"
}
}A prescriber acknowledged the flag. Re-delivery stops.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "clinical.flag_acknowledged" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ClinicalFlagAcknowledgedData) |
{- "id": "0190f5e1-0a1b-7c2d-9e3f-4a5b6c7d8e9f",
- "type": "clinical.flag_acknowledged",
- "created_at": "2026-10-26T14:20:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "acknowledged_at": "2026-10-26T14:20:00Z"
}
}A prescriber resolved the flag and set the enrollment's risk level to new_risk_level.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "clinical.flag_resolved" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ClinicalFlagResolvedData) |
{- "id": "0190f5e2-1c2d-7e3f-8a4b-5c6d7e8f9a02",
- "type": "clinical.flag_resolved",
- "created_at": "2026-10-26T15:10:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "flag_id": "0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6",
- "resolved_at": "2026-10-26T15:10:00Z",
- "new_risk_level": "elevated"
}
}No logs or messages for your brand's disengaged_after_days (7 by default). A nudge from your app may help.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "patient.disengaged" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (PatientDisengagedData) |
{- "id": "0190f5d0-6f70-7182-9394-0e1f2a3b4c56",
- "type": "patient.disengaged",
- "created_at": "2026-11-09T06:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "last_activity_at": "2026-11-02T13:40:00Z",
- "inactive_days": 7
}
}The patient reached a milestone. Celebrate it in your app: read the personal details with
GET /v1/patient/milestones/{milestone_id} using the patient's token. Keep any push text generic.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "patient.milestone_reached" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (PatientMilestoneReachedData) |
{- "id": "0190f5e1-3b4c-7d5e-8f60-a1b2c3d4e5f6",
- "type": "patient.milestone_reached",
- "created_at": "2026-10-25T20:10:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "milestone_id": "0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f",
- "code": "protein_target_days_7",
- "achieved_at": "2026-10-25T20:10:00Z"
}
}A reminder is due. Send the notification in your own words, with no health details in the push text.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "reminder.due" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (ReminderDueData) |
{- "id": "0190f5d1-92a3-74b5-86c7-3b4c5d6e7f8a",
- "type": "reminder.due",
- "created_at": "2026-11-02T13:00:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "reminder_id": "0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45",
- "kind": "injection",
- "due_at": "2026-11-02T13:00:00Z",
- "form_assignment_id": null
}
}A data request finished. For an export, get a download link with POST /v1/data-requests/{data_request_id}/export-link.
| TeraRx-Timestamp required | string^[0-9]{10}$ Example: 1793023500 Unix time in seconds when this delivery attempt was signed. Reject deliveries more than 5 minutes old. |
| TeraRx-Signature required | string^v1=[0-9a-f]{64}(,v1=[0-9a-f]{64})*$ Example: v1=5e9f492e2cee549f0d1688a9d4a6d0505ea1687e5c293eb28a01bad16ee7aa2a
|
| TeraRx-Event-Id required | string <uuid> Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7 The event id, the same as |
| id required | string <uuid> (Uuid) Unique per event; the same on every retry. Dedupe on it. |
| type required | string Enum: "enrollment.created" "enrollment.stage_changed" "enrollment.paused" "enrollment.resumed" "enrollment.cancelled" "care_team.assigned" "care_team.changed" "care_team.capacity_warning" "care_team.feedback_due" "form.requested" "onboarding.completed" "protocol.published" "protocol.updated" "message.created" "clinical.flag_raised" "clinical.flag_redelivered" "clinical.flag_acknowledged" "clinical.flag_resolved" "patient.disengaged" "patient.milestone_reached" "reminder.due" "data_request.completed" Value: "data_request.completed" |
| created_at required | string <date-time> (Timestamp) ISO 8601 in UTC. |
| tenant_id required | string <uuid> (Uuid) The brand (tenant) the event belongs to. |
required | object (DataRequestCompletedData) |
{- "id": "0190f5d1-a3b4-75c6-97d8-4c5d6e7f8a9b",
- "type": "data_request.completed",
- "created_at": "2026-10-26T14:40:00Z",
- "tenant_id": "a0000000-0000-4000-8000-000000000002",
- "data": {
- "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
- "external_patient_id": "brand-pt-48213",
- "data_request_id": "7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13",
- "kind": "export",
- "status": "completed"
}
}