API reference Synthetic data only v1

TeraRx API (1.0.0)

Download OpenAPI specification:

TeraRx support: support@hellotera.com License: Proprietary

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

Try it

  • Sandbox: the sandbox server answers every operation with the examples on this page. It checks that a token is present but not its signature, validates request bodies and stores nothing.
  • Postman: import the collection and the sandbox environment. A pre-request script gets each caller its token.
  • Guides and samples: quickstart, guides and Node and Python code for signing the patient assertion, verifying webhook signatures and enrolling a patient, on the docs site at docs.tera.health.

Conventions

Base URL and versioning

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.

Formats

  • JSON in and out (application/json; application/merge-patch+json for PATCH). UTF-8.
  • Ids are UUID strings. Treat them as opaque.
  • Timestamps are ISO 8601 in UTC (2026-10-26T14:05:00Z). Log dates (log_date) are calendar dates in the patient's time zone.
  • Measurements are always metric (kg, cm, ml, g, kcal). units on the patient is a display preference only.
  • Field names are 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.

Request ids

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.

Errors

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.

Idempotency

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.

  • The same key and the same body within 24 hours returns the first response, with Idempotent-Replayed: true, and creates nothing new.
  • The same key with a different body is refused with 422 idempotency_key_reused.
  • While the first request with a key is still running, a second one gets 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.

Pagination

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.

Rate limits

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

Plans

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.

Privacy

  • A patient's records belong to one brand. Nothing is shared, matched or merged across brands.
  • Brand servers read status and ids, never clinical content. Reports are aggregates with groups under 10 patients suppressed.
  • Webhooks carry ids and the minimum needed to route (status, stage, severity, risk level), never message, form or protocol text. Fetch content through the API.
  • Files are uploaded and downloaded through short-lived pre-signed URLs. Never store those URLs.
  • Every read or change of a coaching record is audit-logged.

Emergencies

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.

Environments

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.

Auth

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.

Brand servers: client credentials

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.

Patients: token exchange

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.

  1. Your app signs the patient in with your own login.
  2. Your server signs a patient assertion (below) and hands it to the app.
  3. The app (or your server) exchanges it:
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
  1. The response's 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.

Patient assertion

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.

Prescriber systems: clinical clients

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.

Get an access token

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.

Authorizations:
oauthClientBasicNone
Request Body schema: application/x-www-form-urlencoded
required
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 invalid_scope.

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 (tenant_required without it, tenant_not_permitted for a brand it may not act for). A brand's own client may omit it or send its own tenant id.

client_id
string

Send only when not using HTTP Basic.

client_secret
string

Send only when not using HTTP Basic.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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 no-store (RFC 6749 section 5.1).

Response Schema: application/json
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"

Request samples

Content type
application/x-www-form-urlencoded
Example
grant_type=client_credentials&scope=enrollments%3Aread%20enrollments%3Awrite%20logs%3Awrite

Response samples

Content type
application/json
Example
{
  • "access_token": "trx_at_example_not_a_real_token",
  • "token_type": "Bearer",
  • "expires_in": 3600,
  • "scope": "enrollments:read enrollments:write logs:write"
}

Tenants

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.

What TeraRx sets up

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

Tokens: one brand at a time

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.

Patient assertions

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.

Listing your brands

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.

Webhooks

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.

Reports and invoices

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.

List the brands this client may act for

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.

Authorizations:
brandServer
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Tenant)

Ordered by slug.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ],
  • "next_cursor": null
}

Enrollments

A patient's enrollment in coaching with one brand. Brand servers create and manage them and read status only, never clinical content.

Enroll a patient

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

Authorizations:
brandServer
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
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 sub of the patient assertions you sign later.

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 prefill_from prefill onboarding forms.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Location
string
Example: "/v1/enrollments/0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5"

URL of the new enrollment.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Request samples

Content type
application/json
{
  • "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": [
      ],
    • "health_profile": {
      }
    },
  • "intake": {
    • "weight_kg": 96.2,
    • "goal_weight_kg": 72.5,
    • "activity_level": "lightly_active"
    },
  • "consents": [
    • {
      }
    ],
  • "treatment": {
    • "medication_id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
    • "dose_mg": 0.25,
    • "phase": "titration",
    • "injection_weekday": 1,
    • "started_on": "2026-09-28"
    }
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "forms": [
    • {
      }
    ],
  • "protocols": [ ]
}

List enrollments

Your brand's enrollments, newest first. Filter by external_patient_id to find one patient's enrollments.

Authorizations:
brandServer
query Parameters
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
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Enrollment)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Get an enrollment

The enrollment's stage, care team status, form status, protocol timestamps and risk level. It carries no clinical content.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ],
  • "protocols": [
    • {
      }
    ]
}

Update the patient's details

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

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
Request Body schema: application/merge-patch+json
required
non-empty
first_name
string (first_name) <= 100 characters
last_name
string (last_name) <= 100 characters
email
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 type_2_diabetes.

object (HealthProfile)

What plans and the AI Coach are built around. Every field is optional. In a PATCH, send only what changes; null clears a value and [] clears a list.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Request samples

Content type
application/merge-patch+json
{
  • "phone": "+12025550178",
  • "state_code": "NJ",
  • "health_profile": {
    • "food_allergies": [
      ]
    }
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "forms": [ ],
  • "protocols": [
    • {
      }
    ]
}

Change plan

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

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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).

Request Body schema: application/json
required
plan_code
required
string (PlanCode)
Enum: "ai_only" "ai_health_fitness"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
reason
string <= 500 characters

Kept on the enrollment history. Do not include health details.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Request samples

Content type
application/json
{
  • "plan_code": "ai_only",
  • "reason": "Patient downgraded at renewal"
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ]
}

Pause

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.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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).

Request Body schema: application/json
optional
reason
string <= 500 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Request samples

Content type
application/json
{
  • "reason": "Patient travelling"
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "forms": [ ],
  • "protocols": [ ]
}

Resume

Returns a paused enrollment to active. Fires enrollment.resumed. Error code: invalid_status_transition (409) unless the enrollment is paused.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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).

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "forms": [ ],
  • "protocols": [
    • {
      }
    ]
}

Cancel

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.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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).

Request Body schema: application/json
required
reason
required
string
Enum: "patient_request" "subscription_ended" "payment_failed" "medically_ineligible" "other"
note
string <= 500 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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 ai_only.

required
Array of objects (FormStatus)
required
Array of objects (ProtocolStatusSummary)

Active protocols.

Request samples

Content type
application/json
{
  • "reason": "subscription_ended"
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "forms": [ ],
  • "protocols": [ ]
}

Ask the AI Coach to reach out

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

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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).

Responses

Response samples

Content type
application/json
{
  • "id": "0190f5e4-5d6e-7f80-9a1b-c2d3e4f5a6b7",
  • "status": "scheduled",
  • "scheduled_for": "2026-10-27T15:00:00Z",
  • "sent_at": null
}

Treatment

The GLP-1 medication, dose and schedule the brand's prescriber set, mirrored by TeraRx and never changed.

List medications

The GLP-1 products you can name in a treatment. It includes the platform catalog plus any added for your brand.

Authorizations:
brandServer

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Medication)

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Update the treatment mirror

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.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
medication_id
required
string <uuid> (Uuid)

From GET /v1/medications.

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 dose_amount, for example units.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 dose_amount, for example units.

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 started_on (week 1 is the first 7 days).

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

note
string or null
created_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

Request samples

Content type
application/json
{
  • "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"
}

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "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"
}

List treatment versions

The mirrored treatment history, newest first. Use it to reconcile with your own records.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Consent

Proof of the legal documents a patient accepted at the brand's checkout.

Record consent proof

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

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
required
Array of objects (ConsentProofInput) [ 1 .. 20 ] items

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
required
Array of objects (ConsentRecord)

Request samples

Content type
application/json
{
  • "consents": [
    • {
      }
    ]
}

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ]
}

List consent records

Every document the patient has accepted for this enrollment, from your checkout or from the app.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (ConsentRecord)

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Brand logs

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.

Send a meal log

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.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
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 POST /v1/patient/meal-photos, after the photo was uploaded. Not returned; read photo_url instead.

entry_method
string
Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan"

How it was entered. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/json
{
  • "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"
}

Response samples

Content type
application/json
{
  • "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"
}

Send a workout log

A workout your app already recorded. A repeated external_id returns the first receipt with 200.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
started_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
required
string <= 50 characters

For example strength, walk, run, yoga, cycling.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/json
{
  • "started_at": "2026-10-26T07:00:00Z",
  • "activity_type": "walk",
  • "duration_min": 30,
  • "external_id": "workout-5531"
}

Response samples

Content type
application/json
{
  • "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"
}

Send a symptom log

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.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
occurred_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
required
string^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/json
{
  • "occurred_at": "2026-10-26T19:45:00Z",
  • "symptom": "constipation",
  • "severity": 3,
  • "external_id": "sym-20931"
}

Response samples

Content type
application/json
{
  • "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"
}

Send an injection log

A dose taken or missed, as your app recorded it. A repeated external_id returns the first receipt with 200.

Authorizations:
brandServer
path Parameters
enrollment_id
required
string <uuid>
Example: 0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
Any of
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

note
string <= 1000 characters
external_id
required
string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$

Your own identifier, printable ASCII without spaces.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/json
{
  • "injected_at": "2026-10-26T08:00:00Z",
  • "scheduled_for": "2026-10-26",
  • "dose_mg": 0.5,
  • "site": "thigh_right",
  • "external_id": "inj-88213"
}

Response samples

Content type
application/json
{
  • "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"
}

Correct a meal log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Request Body schema: application/merge-patch+json
required
non-empty
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 POST /v1/patient/meal-photos, after the photo was uploaded. Not returned; read photo_url instead.

entry_method
string (entry_method)
Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan"

How it was entered. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "protein_g": 30,
  • "calories_kcal": 360
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a meal log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Correct a workout log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Request Body schema: application/merge-patch+json
required
non-empty
started_at
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
string (activity_type) <= 50 characters

For example strength, walk, run, yoga, cycling.

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

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "duration_min": 45
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a workout log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Correct a symptom log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Request Body schema: application/merge-patch+json
required
non-empty
occurred_at
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
string (symptom) ^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "severity": 3,
  • "note": "Worse after dinner than I first logged"
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a symptom log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Correct an injection log

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.

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Request Body schema: application/merge-patch+json
required
non-empty
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

note
string (note-2) <= 1000 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "missed": false,
  • "injected_at": "2026-10-26T08:00:00Z",
  • "dose_mg": 0.5
}

Response samples

Content type
application/json
{
  • "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"
}

Delete an injection log

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

Authorizations:
brandServer
path Parameters
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 external_id for the log, URL-encoded.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Send daily values

Weight, body measures, water, steps, sleep and other daily values your app recorded. Only the fields sent change.

Authorizations:
brandServer
path Parameters
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.

Request Body schema: application/merge-patch+json
required
non-empty
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 <protocol_id>:<habit_key>, from the active protocols.

object

Any other daily metric, keyed by a metric code. New codes need no API change.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/merge-patch+json
{
  • "weight_kg": 94.8,
  • "steps": 7400
}

Response samples

Content type
application/json
{
  • "log_date": "2026-10-26",
  • "updated_at": "2026-10-26T14:05:00Z"
}

Events

Replay of the webhook events for the last 30 days, in order.

Replay events

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.

Authorizations:
brandServer
query Parameters
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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of any (Event)

Oldest first, in the order they were written.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ],
  • "next_cursor": null
}

Get an event

One event by id, within the 30-day window.

Authorizations:
brandServer
path Parameters
event_id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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)

Response samples

Content type
application/json
{
  • "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"
    }
}

Reports

The aggregate brand report. Groups under 10 patients are suppressed.

Get the brand report

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

Authorizations:
brandServer
query Parameters
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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema:
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

Response samples

Content type
{
  • "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": [
    • {
      },
    • {
      }
    ]
}

Data requests

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.

Create a data request

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

Authorizations:
brandServer
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
enrollment_id
required
string <uuid> (Uuid)
kind
required
string

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • export: a copy of the patient's coaching records for your brand, as a download.
  • delete: removes the patient's coaching records for your brand, except what the law requires TeraRx to keep (the audit log).
  • amend: the HIPAA right to amend. You decide within 60 days of requested_at.
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.

Request samples

Content type
application/json
Example
{
  • "enrollment_id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
  • "kind": "export"
}

Response samples

Content type
application/json
{
  • "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
}

List data requests

Your brand's data requests, newest first.

Authorizations:
brandServer
query Parameters
status
string (DataRequestStatus)
Enum: "received" "processing" "completed" "rejected"
Example: status=processing
kind
string (DataRequestKind)
Enum: "export" "delete" "amend"
Example: kind=amend
  • export: a copy of the patient's coaching records for your brand, as a download.
  • delete: removes the patient's coaching records for your brand, except what the law requires TeraRx to keep (the audit log).
  • amend: the HIPAA right to amend. You decide within 60 days of requested_at.
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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (DataRequest)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Get a data request

The request's status and, for exports, whether the download is ready.

Authorizations:
brandServer
path Parameters
data_request_id
required
string <uuid>
Example: 7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"
  • export: a copy of the patient's coaching records for your brand, as a download.
  • delete: removes the patient's coaching records for your brand, except what the law requires TeraRx to keep (the audit log).
  • amend: the HIPAA right to amend. You decide within 60 days of requested_at.
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.

Response samples

Content type
application/json
{
  • "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
}

Decide an amendment

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

Authorizations:
brandServer
path Parameters
data_request_id
required
string <uuid>
Example: 7a2e4c61-0b3d-4f58-9a17-2c4e6f8a0b13
header Parameters
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).

Request Body schema: application/json
required
decision
required
string
Value: "accepted"
decision_note
string <= 2000 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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"
  • export: a copy of the patient's coaching records for your brand, as a download.
  • delete: removes the patient's coaching records for your brand, except what the law requires TeraRx to keep (the audit log).
  • amend: the HIPAA right to amend. You decide within 60 days of requested_at.
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.

Request samples

Content type
application/json
Example
{
  • "decision": "accepted",
  • "decision_note": "Corrected the severity to 2."
}

Response samples

Content type
application/json
{
  • "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": {
      },
    • "respond_by": "2026-12-25T14:05:00Z",
    • "decision": "accepted",
    • "decision_note": "Corrected the severity to 2.",
    • "decided_at": "2026-10-28T10:00:00Z"
    }
}

Patient

The patient's profile, settings, treatment and legal documents.

Get my profile

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
object
required
object
required
Array of objects (CareTeamMember)

Empty on ai_only and until coaches are assigned.

required
object (BrandInfo)
legal_documents_pending
integer

Required legal documents not accepted yet. When above 0, show GET /v1/patient/legal-documents first.

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "health_profile": {
      }
    },
  • "enrollment": {
    • "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
    • "plan_code": "ai_health_fitness",
    • "status": "active",
    • "stage": "protocols_active",
    • "data_sharing": true
    },
  • "care_team": [
    • {
      }
    ],
  • "brand": {
    • "display_name": "Demo Brand",
    • "primary_color": "#1A5F7A",
    • "support": {}
    },
  • "legal_documents_pending": 0
}

Update my profile

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

Authorizations:
patientToken
Request Body schema: application/merge-patch+json
required
non-empty
height_cm
number [ 50 .. 260 ]

The patient's height. Metric, like every measurement; convert from units in the app.

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 PATCH, send only what changes; null clears a value and [] clears a list.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
object
required
object
required
Array of objects (CareTeamMember)

Empty on ai_only and until coaches are assigned.

required
object (BrandInfo)
legal_documents_pending
integer

Required legal documents not accepted yet. When above 0, show GET /v1/patient/legal-documents first.

Request samples

Content type
application/merge-patch+json
{
  • "height_cm": 167.6,
  • "units": "metric",
  • "health_profile": {
    • "dietary_restrictions": [
      ],
    • "cooking": {
      }
    }
}

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "health_profile": {
      }
    },
  • "enrollment": {
    • "id": "0190f5c2-3a1b-7c4d-8e9f-a0b1c2d3e4f5",
    • "plan_code": "ai_health_fitness",
    • "status": "active",
    • "stage": "protocols_active",
    • "data_sharing": true
    },
  • "care_team": [
    • {
      }
    ],
  • "brand": {
    • "display_name": "Demo Brand",
    • "primary_color": "#1A5F7A",
    • "support": {}
    },
  • "legal_documents_pending": 0
}

Get my treatment

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 dose_amount, for example units.

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.

Response samples

Content type
application/json
{
  • "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"
}

Report my treatment

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
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>

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 dose_amount, for example units.

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.

Request samples

Content type
application/json
{
  • "medication_id": "6f1d2c3b-4a59-4e8f-9d7c-1b2a3c4d5e6f",
  • "dose_mg": 0.5,
  • "phase": "titration",
  • "injection_weekday": 1,
  • "started_on": "2026-09-28"
}

Response samples

Content type
application/json
{
  • "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"
}

List my legal documents

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (LegalDocument)

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Accept a legal document

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
legal_document_id
required
string <uuid> (Uuid)

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

Request samples

Content type
application/json
{
  • "legal_document_id": "4f6a8c0e-2b1d-4e3f-8a5b-9c1d3e5f7a9b"
}

Response samples

Content type
application/json
{
  • "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"
}

Progress

Today at a glance, targets for a day, a week's stats and the milestones the patient reached.

Get today

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
date
required
string <date> (LocalDate)

A calendar date in the patient's time zone.

treatment_week
required
integer or null

The same number as GET /v1/patient/treatment. Null before a treatment is set.

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 (sent or in_progress).

unread_care_messages
required
integer

Across care threads. Always 0 on ai_only.

legal_documents_pending
required
integer

Required legal documents not accepted yet. When above 0, show GET /v1/patient/legal-documents first.

required
Array of objects (Milestone)

Milestones reached in the last 7 days, newest first.

Response samples

Content type
application/json
{
  • "date": "2026-10-27",
  • "treatment_week": 6,
  • "injection": {
    • "due_today": true,
    • "logged": false
    },
  • "targets": [
    • {
      },
    • {
      }
    ],
  • "meals": [
    • {
      },
    • {
      }
    ],
  • "workouts": [
    • {
      }
    ],
  • "habits": [
    • {
      }
    ],
  • "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": [ ]
}

Get targets and progress for a day

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

Authorizations:
patientToken
query Parameters
date
string <date>
Example: date=2026-10-27

A day in the patient's time zone. Defaults to today.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Response samples

Content type
application/json
{
  • "date": "2026-10-27",
  • "targets": [
    • {
      },
    • {
      },
    • {
      }
    ]
}

Get a week's stats

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

Authorizations:
patientToken
query Parameters
week_number
integer [ 1 .. 520 ]
Example: week_number=6

Omit for the current week.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
week_number
required
integer >= 1

The treatment week, the same number as treatment_week on GET /v1/patient/treatment.

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.

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ]
}

List my milestones

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Milestone)

Newest first. At most one per code.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Get a milestone

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

Authorizations:
patientToken
path Parameters
milestone_id
required
string <uuid>
Example: 0190f5e1-2a3b-7c4d-9e5f-6a7b8c9d0e1f

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Code Reached when
first_meal_logged The first meal is logged
logging_streak_7, logging_streak_30 Something is logged 7 or 30 days in a row
protein_target_days_7, protein_target_days_30 7 or 30 days at 80% or more of the protein target
workouts_10, workouts_50 10 or 50 workouts logged
injections_on_time_4, injections_on_time_12 4 or 12 doses in a row logged on their day
week_4_completed, week_12_completed 4 or 12 weeks since enrollment
weight_loss_5_pct, weight_loss_10_pct 5% or 10% below the first weight logged
goal_weight_reached At or below the health profile's goal_weight_kg
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 stats.

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.

Response samples

Content type
application/json
{
  • "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

Onboarding, check-in and eligibility forms, pinned to the version they were sent with.

List my forms

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

Authorizations:
patientToken
query Parameters
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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (FormAssignmentSummary)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Get a form

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

Authorizations:
patientToken
path Parameters
form_assignment_id
required
string <uuid>
Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "prefill": {
    • "weight_kg": 96.2
    },
  • "draft_answers": {
    • "primary_goal": "preserve_muscle"
    },
  • "answers": null,
  • "result": null
}

Save a draft

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

Authorizations:
patientToken
path Parameters
form_assignment_id
required
string <uuid>
Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2
Request Body schema: application/json
required
required
object (Answers)

Answers keyed by field id.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/json
{
  • "answers": {
    • "primary_goal": "preserve_muscle"
    }
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "prefill": {
    • "weight_kg": 96.2
    },
  • "draft_answers": {
    • "primary_goal": "preserve_muscle"
    },
  • "answers": null,
  • "result": null
}

Submit a form

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

Authorizations:
patientToken
path Parameters
form_assignment_id
required
string <uuid>
Example: 0190f5c3-7e21-7a40-9b11-5d6e7f8091a2
header Parameters
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).

Request Body schema: application/json
required
required
object (Answers)

Answers keyed by field id.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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.

Request samples

Content type
application/json
{
  • "answers": {
    • "weight_kg": 96.2,
    • "primary_goal": "preserve_muscle"
    }
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ],
  • "prefill": {
    • "weight_kg": 96.2
    },
  • "draft_answers": null,
  • "answers": {
    • "weight_kg": 96.2,
    • "primary_goal": "preserve_muscle"
    },
  • "result": null
}

Logging

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.

List my daily logs

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

Authorizations:
patientToken
query Parameters
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 from.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Log daily values

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

Authorizations:
patientToken
path Parameters
log_date
required
string <date>
Example: 2026-10-26

The day, in the patient's time zone.

Request Body schema: application/merge-patch+json
required
non-empty
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 <protocol_id>:<habit_key>, from the active protocols.

object

Any other daily metric, keyed by a metric code. New codes need no API change.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/merge-patch+json
{
  • "water_ml": 1800,
  • "weight_kg": 94.8,
  • "mood": 4,
  • "habits_done": {
    • "0190f5c6-8b9c-7d01-8e23-4f5a6b7c8d90:protein_each_meal": true
    }
}

Response samples

Content type
application/json
{
  • "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"
}

Start a meal photo upload

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
content_type
required
string
Enum: "image/jpeg" "image/png" "image/heic"
content_length
required
integer [ 1 .. 10485760 ]

Bytes, at most 10 MB.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
upload_id
required
string <uuid> (Uuid)
upload_url
required
string <uri>

Pre-signed URL. Upload the bytes with method and headers before expires_at.

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.

Request samples

Content type
application/json
{
  • "content_type": "image/jpeg",
  • "content_length": 1843200
}

Response samples

Content type
application/json
{}

Analyze a meal photo

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

Authorizations:
patientToken
path Parameters
upload_id
required
string <uuid>
Example: 8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e

From POST /v1/patient/meal-photos.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 low.

note
string or null

Something the patient should check, for example a sauce the photo can't show.

Response samples

Content type
application/json
{
  • "upload_id": "8e2c4a60-1f3b-4d5e-9a7c-0b2d4f6a8c1e",
  • "description": "Grilled chicken salad with quinoa",
  • "meal_type": "lunch",
  • "ingredients": [
    • {
      },
    • {
      },
    • {
      }
    ],
  • "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."
}

List my recent foods

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

Authorizations:
patientToken
query Parameters
limit
integer [ 1 .. 50 ]
Default: 10
Example: limit=10

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (RecentFood)

Most recently logged first.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ],
  • "next_cursor": "eyJpZCI6IjAxOTBmNWUyIn0"
}

Log a meal

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
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 POST /v1/patient/meal-photos, after the photo was uploaded. Not returned; read photo_url instead.

entry_method
string
Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan"

How it was entered. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/json
{
  • "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
}

Response samples

Content type
application/json
{
  • "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"
}

List my meals

Meal logs in a date range, newest first.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
query Parameters
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 from.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (MealLog)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Correct a meal log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Request Body schema: application/merge-patch+json
required
non-empty
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 POST /v1/patient/meal-photos, after the photo was uploaded. Not returned; read photo_url instead.

entry_method
string (entry_method)
Enum: "manual" "photo" "photo_ai" "text_ai" "barcode" "plan"

How it was entered. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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. photo_ai when the form was pre-filled by POST /v1/patient/meal-photos/{upload_id}/analysis (send the same photo_upload_id); TeraRx then compares what was saved with the estimate.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "protein_g": 30,
  • "calories_kcal": 360
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a meal log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Log a workout

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
started_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
required
string <= 50 characters

For example strength, walk, run, yoga, cycling.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
started_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
required
string <= 50 characters

For example strength, walk, run, yoga, cycling.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/json
{
  • "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": [
    • {
      }
    ]
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ],
  • "source": "brand_app",
  • "created_at": "2026-10-26T07:40:00Z",
  • "updated_at": "2026-10-26T07:40:00Z"
}

List my workouts

Workout logs in a date range, newest first.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
query Parameters
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 from.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (WorkoutLog)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Correct a workout log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Request Body schema: application/merge-patch+json
required
non-empty
started_at
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
string (activity_type) <= 50 characters

For example strength, walk, run, yoga, cycling.

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

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
started_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

activity_type
required
string <= 50 characters

For example strength, walk, run, yoga, cycling.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "duration_min": 45
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a workout log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Log a symptom

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
occurred_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
required
string^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
occurred_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
required
string^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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 symptom). When true, show the emergency guidance (911, 988) right away; the clinical flag to the care team or prescriber follows.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/json
{
  • "occurred_at": "2026-10-26T19:45:00Z",
  • "symptom": "nausea",
  • "severity": 2,
  • "note": "Mild, after dinner"
}

Response samples

Content type
application/json
{
  • "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"
}

List my symptoms

Symptom logs in a date range, newest first.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
query Parameters
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 from.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (SymptomLog)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Correct a symptom log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Request Body schema: application/merge-patch+json
required
non-empty
occurred_at
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
string (symptom) ^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
occurred_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

symptom
required
string^[a-z][a-z0-9_]*$

A symptom code, for example nausea, vomiting, diarrhea, constipation, reflux, fatigue, abdominal_pain. These codes are red flags at any severity: chest_pain, difficulty_breathing, fainting, severe_abdominal_pain, persistent_vomiting, blood_in_vomit, blood_in_stool, yellow_skin_or_eyes, severe_dehydration, allergic_reaction, swelling_face_or_throat, vision_changes, suicidal_thoughts, self_harm. Any symptom at severity 5 is one too. A brand's own alert rules can add codes.

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 symptom). When true, show the emergency guidance (911, 988) right away; the clinical flag to the care team or prescriber follows.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "severity": 3,
  • "note": "Worse after dinner than I first logged"
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a symptom log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Log an injection

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
Any of
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

note
string <= 1000 characters
external_id
string (ExternalId) [ 1 .. 128 ] characters ^[\x21-\x7E]+$

Your own identifier, printable ASCII without spaces.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
Any of
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/json
Example
{
  • "injected_at": "2026-10-26T08:00:00Z",
  • "scheduled_for": "2026-10-26",
  • "dose_mg": 0.5,
  • "site": "abdomen_left"
}

Response samples

Content type
application/json
{
  • "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"
}

List my injections

Injection logs in a date range, newest first.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
query Parameters
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 from.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of InjectionLog (object) or InjectionLog (object) (InjectionLog)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Correct an injection log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Request Body schema: application/merge-patch+json
required
non-empty
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

note
string (note-2) <= 1000 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
Any of
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 abdomen_left, abdomen_right, thigh_left, thigh_right, arm_left, arm_right.

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. brand_api for brand servers, brand_app for patient tokens issued to a brand.

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 created_at until then.

Request samples

Content type
application/merge-patch+json
{
  • "missed": false,
  • "injected_at": "2026-10-26T08:00:00Z",
  • "dose_mg": 0.5
}

Response samples

Content type
application/json
{
  • "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"
}

Delete an injection log

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

Authorizations:
patientToken
path Parameters
log_id
required
string <uuid>
Example: 0190f5c9-5d6e-7f70-8a92-1b2c3d4e5f60

The log's id.

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Protocols and plans

Published protocols, meal plans and workout plans, read only. Drafts are never visible to patients.

List my active protocols

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (ProtocolSummary)

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Get my active protocol of a type

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

Authorizations:
patientToken
path Parameters
protocol_type
required
string (ProtocolType)
Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements"
Example: nutrition

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"

full_plan protocols come with a meal or workout 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"

coach when a coach approved it; ai_coach when the AI Coach published it on its own (ai_only with ai_autonomy = auto).

change_reason
string or null or string or null

Why this version replaced the one before it. Null on version 1.

required
object

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "habits": [
      ],
    • "meal_plan_id": "0190f5ca-1a2b-7c3d-8e4f-5a6b7c8d9e01",
    • "workout_plan_id": null
    }
}

List versions of a protocol

The active version and the superseded ones before it.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
path Parameters
protocol_type
required
string (ProtocolType)
Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements"
Example: nutrition
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (ProtocolSummary)

Newest version first.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ],
  • "next_cursor": null
}

Get one version of a protocol

Any published version, active or superseded.

Plans: AI + health and fitness coaches (ai_health_fitness), AI only (ai_only).

Authorizations:
patientToken
path Parameters
protocol_type
required
string (ProtocolType)
Enum: "nutrition" "fitness" "sleep" "stress" "lifestyle" "supplements"
Example: nutrition
version
required
integer >= 1
Example: 2

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"

full_plan protocols come with a meal or workout 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"

coach when a coach approved it; ai_coach when the AI Coach published it on its own (ai_only with ai_autonomy = auto).

change_reason
string or null or string or null

Why this version replaced the one before it. Null on version 1.

required
object

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "habits": [
      ],
    • "meal_plan_id": null,
    • "workout_plan_id": null
    }
}

Get my meal plan

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"

coach when a coach approved it; ai_coach when the AI Coach published it on its own (ai_only with ai_autonomy = auto).

required
Array of objects (MealPlanItem)

Ordered by day_index, then sort_order.

Response samples

Content type
application/json
{
  • "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": [
    • {
      },
    • {
      }
    ]
}

Get my workout plan

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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"

coach when a coach approved it; ai_coach when the AI Coach published it on its own (ai_only with ai_autonomy = auto).

required
Array of objects (WorkoutPlanItem)

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ]
}

Rate a meal on my plan

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

Authorizations:
patientToken
path Parameters
item_id
required
string <uuid>
Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01
Request Body schema: application/json
required
rating
required
integer [ 1 .. 5 ]
comment
string or null <= 500 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
rating
required
integer [ 1 .. 5 ]
comment
string or null
updated_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

Request samples

Content type
application/json
{
  • "rating": 4,
  • "comment": "Filling, but I'd like it less spicy."
}

Response samples

Content type
application/json
{
  • "rating": 4,
  • "comment": "Filling, but I'd like it less spicy.",
  • "updated_at": "2026-10-27T13:05:00Z"
}

Clear a meal rating

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

Authorizations:
patientToken
path Parameters
item_id
required
string <uuid>
Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Meal swap

The AI Coach swaps a meal for one that stays within the protocol and TeraRx's clinical rules.

Ask the AI Coach for swap options

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

Authorizations:
patientToken
path Parameters
item_id
required
string <uuid>
Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01
header Parameters
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).

Request Body schema: application/json
optional
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

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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 then says why.

note
string or null
expires_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

Request samples

Content type
application/json
{
  • "reason": "I don't like fish",
  • "avoid": [
    • "fish"
    ]
}

Response samples

Content type
application/json
{
  • "item_id": "4d2f9a31-6c8b-4e0d-9f72-1b3c5d7e9f02",
  • "options": [
    • {
      },
    • {
      }
    ],
  • "note": null,
  • "expires_at": "2026-10-26T11:30:00Z"
}

Swap a meal

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

Authorizations:
patientToken
path Parameters
item_id
required
string <uuid>
Example: 3c1e8f20-5b7a-4d9c-8e61-0a2b4c6d8e01
header Parameters
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).

Request Body schema: application/json
required
option_id
required
string
reason
string <= 500 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
id
required
string <uuid> (Uuid)
day_index
required
integer >= 0

Day in the plan's cycle, starting at 0 on starts_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 GET /v1/patient/library/{library_item_id}.

MealSwapRecord (object) or null
MealRating (object) or null

The patient's rating of this meal, or null.

Request samples

Content type
application/json
{
  • "option_id": "opt_1",
  • "reason": "I don't like fish"
}

Response samples

Content type
application/json
{
  • "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"
    }
}

Library

Recipes and exercises that plans link to, that the patient can browse, save and rate.

Browse recipes and exercises

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

Authorizations:
patientToken
query Parameters
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 high_protein.

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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (LibraryItemSummary)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [],
  • "next_cursor": null
}

Get a recipe or exercise

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

Authorizations:
patientToken
path Parameters
library_item_id
required
string <uuid>
Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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 high_protein, fiber_rich, low_appetite, mild_nausea, heartburn_friendly and dose_day on recipes. New tags may be added.

required
Array of objects (LibraryMedia)
RecipeDetails (object) or null

Set when kind is recipe.

ExerciseDetails (object) or null

Set when kind is exercise.

favorite
required
boolean

True when the patient saved it.

required
MealRating (object) or null

The patient's rating of a recipe, or null.

Response samples

Content type
application/json
Example
{
  • "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"
    ],
  • "recipe": {
    • "servings": 1,
    • "serving_size": "1 bowl (about 300 g)",
    • "prep_minutes": 5,
    • "cook_minutes": 0,
    • "difficulty": "easy",
    • "cuisine": null,
    • "meal_types": [
      ],
    • "appliances": [ ],
    • "ingredients": [
      ],
    • "steps": [
      ],
    • "nutrients": {
      },
    • "allergens": [
      ],
    • "diets": [
      ],
    • "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"
    }
}

Save a recipe or exercise

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

Authorizations:
patientToken
path Parameters
library_item_id
required
string <uuid>
Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Remove a favorite

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

Authorizations:
patientToken
path Parameters
library_item_id
required
string <uuid>
Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Rate a recipe

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

Authorizations:
patientToken
path Parameters
library_item_id
required
string <uuid>
Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f
Request Body schema: application/json
required
rating
required
integer [ 1 .. 5 ]
comment
string or null <= 500 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
rating
required
integer [ 1 .. 5 ]
comment
string or null
updated_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

Request samples

Content type
application/json
{
  • "rating": 2,
  • "comment": "Too sweet for me."
}

Response samples

Content type
application/json
{
  • "rating": 2,
  • "comment": "Too sweet for me.",
  • "updated_at": "2026-10-27T12:40:00Z"
}

Clear a recipe rating

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

Authorizations:
patientToken
path Parameters
library_item_id
required
string <uuid>
Example: 2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Care messaging

Threads with the coaches on the coached plan, photos, editing the last message, read state and reactions.

List my care threads

One thread per coach on the care team, with unread counts.

Plans: AI + health and fitness coaches (ai_health_fitness).

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (CareConversation)

One thread per coach on the care team.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ]
}

List messages in a care thread

Newest first. New messages are announced by message.created; fetch them here.

Plans: AI + health and fitness coaches (ai_health_fitness).

Authorizations:
patientToken
path Parameters
conversation_id
required
string <uuid>
Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Message)

Newest first.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": "eyJpZCI6IjAxOTBmNWM0In0"
}

Message my coach

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

Authorizations:
patientToken
path Parameters
conversation_id
required
string <uuid>
Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
Any of
body
required
string [ 1 .. 4000 ] characters
photo_upload_id
string <uuid> (Uuid)

From POST /v1/patient/message-photos, after the photo was uploaded. One photo per message.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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.

Request samples

Content type
application/json
{
  • "body": "Nausea is better today. Can I swap dinner?"
}

Response samples

Content type
application/json
{
  • "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
}

Mark a care thread read

Moves the patient's read marker. The coach sees their messages marked as read.

Plans: AI + health and fitness coaches (ai_health_fitness).

Authorizations:
patientToken
path Parameters
conversation_id
required
string <uuid>
Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12
Request Body schema: application/json
optional
read_at
string <date-time> (Timestamp)

Defaults to now. Everything sent up to this time counts as read.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
conversation_id
required
string <uuid> (Uuid)
patient_last_read_at
required
string <date-time> (Timestamp)

ISO 8601 in UTC.

unread_count
required
integer

Request samples

Content type
application/json
{
  • "read_at": "2026-10-26T14:05:00Z"
}

Response samples

Content type
application/json
{
  • "conversation_id": "0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12",
  • "patient_last_read_at": "2026-10-26T14:05:00Z",
  • "unread_count": 0
}

Edit my last message

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

Authorizations:
patientToken
path Parameters
message_id
required
string <uuid>
Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23
Request Body schema: application/merge-patch+json
required
body
required
string [ 1 .. 4000 ] characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/merge-patch+json
{
  • "body": "Nausea is better today. Can I swap tonight's dinner?"
}

Response samples

Content type
application/json
{
  • "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
}

Set my reactions on a message

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

Authorizations:
patientToken
path Parameters
message_id
required
string <uuid>
Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23
Request Body schema: application/json
required
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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
message_id
required
string <uuid> (Uuid)
required
Array of objects (Reaction)

Request samples

Content type
application/json
{
  • "emojis": [
    • "👍"
    ]
}

Response samples

Content type
application/json
{
  • "message_id": "0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23",
  • "reactions": [
    • {
      }
    ]
}

Start a photo upload for my coach

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
content_type
required
string
Enum: "image/jpeg" "image/png" "image/heic"
content_length
required
integer [ 1 .. 10485760 ]

Bytes, at most 10 MB.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
upload_id
required
string <uuid> (Uuid)
upload_url
required
string <uri>

Pre-signed URL. Upload the bytes with method and headers before expires_at.

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.

Request samples

Content type
application/json
{
  • "content_type": "image/jpeg",
  • "content_length": 2097152
}

Response samples

Content type
application/json
{}

Feedback

Popups that ask the patient about their coach. TeraRx writes the questions and decides when to ask.

List open feedback popups

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (FeedbackRequest)

Popups waiting for an answer, oldest first.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ]
}

Get a feedback popup

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

Authorizations:
patientToken
path Parameters
feedback_request_id
required
string <uuid>
Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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, health_coach or fitness_coach. New roles may be added.

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>

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "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": [
      ]
    },
  • "requested_at": "2026-10-27T15:00:00Z",
  • "expires_at": "2026-11-10T15:00:00Z",
  • "answers": null,
  • "answered_at": null
}

Answer a feedback popup

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

Authorizations:
patientToken
path Parameters
feedback_request_id
required
string <uuid>
Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6
header Parameters
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).

Request Body schema: application/json
required
required
object (FeedbackAnswers)

One value per question key; leave out questions the patient skipped.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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, health_coach or fitness_coach. New roles may be added.

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>

Request samples

Content type
application/json
{
  • "answers": {
    • "overall": 5,
    • "helpful": "up",
    • "more_help_with": "side_effects",
    • "comment": "Maya's tips for nausea made a real difference."
    }
}

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "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": [
      ]
    },
  • "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"
}

Dismiss a feedback popup

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

Authorizations:
patientToken
path Parameters
feedback_request_id
required
string <uuid>
Example: 0190f5e3-4c5d-7e6f-8a90-b1c2d3e4f5a6
header Parameters
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).

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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, health_coach or fitness_coach. New roles may be added.

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>

Response samples

Content type
application/json
{
  • "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": [
      ],
    • "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": [
      ]
    },
  • "requested_at": "2026-10-27T15:00:00Z",
  • "expires_at": "2026-11-10T15:00:00Z",
  • "answers": null,
  • "answered_at": null
}

AI Coach

Chat with the AI Coach on every plan, and what it remembers. Replies stream or can be polled.

List my AI Coach chats

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

Authorizations:
patientToken
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (AiCoachConversation)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Start an AI Coach chat

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

Authorizations:
patientToken
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
optional
title
string <= 120 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
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>

Request samples

Content type
application/json
{
  • "title": "Dinner ideas"
}

Response samples

Content type
application/json
{
  • "id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
  • "title": "Dinner ideas",
  • "created_at": "2026-10-26T11:00:00Z",
  • "last_message_at": null
}

List messages in an AI Coach chat

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

Authorizations:
patientToken
path Parameters
conversation_id
required
string <uuid>
Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Page size.

cursor
string <= 512 characters
Example: cursor=eyJpZCI6IjAxOTBmNWMyIn0

The next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Message)

Newest first.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Message the AI Coach

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

Authorizations:
patientToken
path Parameters
conversation_id
required
string <uuid>
Example: 0190f5c4-1d2e-7f30-8a45-6b7c8d9e0f12
header Parameters
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 Idempotent-Replayed: true) instead of creating a duplicate. The same key with a different body is refused with 422 idempotency_key_reused.

Request Body schema: application/json
required
body
required
string [ 1 .. 4000 ] characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
required
object (Message)
required
object (AiCoachReply)

Request samples

Content type
application/json
{
  • "body": "What can I eat tonight instead of salmon?"
}

Response samples

Content type
application/json
{
  • "message": {
    • "id": "0190f5cc-4d5e-7f60-8172-8b9c0d1e2f34",
    • "conversation_id": "0190f5cc-3c4d-7e5f-8061-7a8b9c0d1e23",
    • "sender": {
      },
    • "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
    }
}

Get the AI Coach's reply

message_id is the patient's message. Two ways to read the reply:

  • Poll with Accept: application/json every 1-2 seconds until status is completed or failed.
  • Stream with 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).

Authorizations:
patientToken
path Parameters
message_id
required
string <uuid>
Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema:
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 completed.

AiCoachEscalation (object) or null
show_emergency_guidance
required
boolean

When true, show the 911 and 988 guidance above the reply.

Response samples

Content type
Example
{
  • "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": {
      },
    • "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
}

Rate an AI Coach reply

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

Authorizations:
patientToken
path Parameters
message_id
required
string <uuid>
Example: 0190f5c4-2e3f-7a41-9b56-7c8d9e0f1a23
Request Body schema: application/json
required
rating
required
string
Enum: "up" "down"
note
string <= 1000 characters

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
message_id
required
string <uuid> (Uuid)
rating
required
string
Enum: "up" "down"
note
string or null

Request samples

Content type
application/json
{
  • "rating": "down",
  • "note": "The swap still had fish in it"
}

Response samples

Content type
application/json
{
  • "message_id": "0190f5cc-5e6f-7071-9283-9c0d1e2f3a45",
  • "rating": "down",
  • "note": "The swap still had fish in it"
}

List what the AI Coach remembers

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (AiCoachMemory)

Newest first.

Response samples

Content type
application/json
{
  • "data": [
    • {
      },
    • {
      }
    ]
}

Confirm a memory

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

Authorizations:
patientToken
path Parameters
memory_id
required
string <uuid>
Example: 6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b
Request Body schema: application/merge-patch+json
required
confirmed
required
boolean

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/merge-patch+json
{
  • "confirmed": true
}

Response samples

Content type
application/json
{
  • "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"
}

Forget a memory

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

Authorizations:
patientToken
path Parameters
memory_id
required
string <uuid>
Example: 6c8e0a2b-4d6f-4a1c-8e3b-5d7f9a1c3e5b

Responses

Response samples

Content type
application/problem+json
{
  • "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"
}

Reminders

Upcoming injection, check-in, logging and habit reminders, and the patient's reminder settings, including whether the AI Coach may start conversations.

List my reminders

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

Authorizations:
patientToken
query Parameters
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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (Reminder)

Soonest first.

next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Snooze a reminder

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

Authorizations:
patientToken
path Parameters
reminder_id
required
string <uuid>
Example: 0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45
Request Body schema: application/json
required
minutes
required
integer [ 5 .. 1440 ]

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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.

Request samples

Content type
application/json
{
  • "minutes": 60
}

Response samples

Content type
application/json
{
  • "id": "0190f5c5-4a5b-7c61-8d78-9e0f1a2b3c45",
  • "kind": "injection",
  • "ref_key": null,
  • "due_at": "2026-11-02T14:00:00Z",
  • "status": "scheduled",
  • "recurring": true
}

Get my reminder settings

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

Authorizations:
patientToken

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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

Response samples

Content type
application/json
{
  • "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"
    }
}

Change my reminder settings

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

Authorizations:
patientToken
Request Body schema: application/merge-patch+json
required
non-empty
object (ReminderKindSetting)
object (CheckInReminderSetting)
object (ReminderKindSetting)
object (ReminderKindSetting)
object (AiCoachOutreachSetting)
QuietHours (object) or null

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
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

Request samples

Content type
application/merge-patch+json
{
  • "habit": {
    • "enabled": false
    },
  • "quiet_hours": {
    • "start": "21:30",
    • "end": "07:30"
    }
}

Response samples

Content type
application/json
{
  • "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"
    }
}

Clinical flags

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.

List clinical flags

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.

Authorizations:
clinicalClient
query Parameters
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 next_cursor from the previous page. Omit it for the first page.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
required
Array of objects (ClinicalFlag)
next_cursor
required
string or null (NextCursor)

Pass it back as cursor for the next page. null on the last page.

Response samples

Content type
application/json
{
  • "data": [
    • {
      }
    ],
  • "next_cursor": null
}

Get a clinical flag

The flag's symptom, severity, triggering records, a timeline around them, the treatment and any coach note.

Authorizations:
clinicalClient
path Parameters
flag_id
required
string <uuid>
Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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.

Response Schema: application/json
id
required
string <uuid> (Uuid)
status
required
string (ClinicalFlagStatus)
Enum: "open" "acknowledged" "resolved" "dismissed"

open until a prescriber acknowledges it; acknowledged until resolved. dismissed flags were closed by TeraRx as raised in error and never change risk.

severity
required
string (Severity)
Enum: "info" "warning" "critical"
title
required
string
rule_code
string or null

The alert rule that raised it, for example acute_gi, red_flag_symptom, moderate_side_effects.

source
required
string
Enum: "alert_rule" "ai_coach_escalation" "coach"

An alert rule on the logs, the AI Coach escalating a conversation (ai_only), or a coach.

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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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, clinical.flag_redelivered is sent.

redelivery_count
required
integer

How many times the flag has been re-delivered for a missed ack_due_at.

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.

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ],
  • "timeline": [
    • {
      }
    ],
  • "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"
}

Acknowledge a flag

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.

Authorizations:
clinicalClient
path Parameters
flag_id
required
string <uuid>
Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6
header Parameters
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).

Request Body schema: application/json
required
required
Prescriber (object) or Prescriber (object) (Prescriber)

The individual prescriber acting through a clinical client. name and at least one of npi or prescriber_id are required. Stored on the flag with the client that sent it.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
id
required
string <uuid> (Uuid)
status
required
string (ClinicalFlagStatus)
Enum: "open" "acknowledged" "resolved" "dismissed"

open until a prescriber acknowledges it; acknowledged until resolved. dismissed flags were closed by TeraRx as raised in error and never change risk.

severity
required
string (Severity)
Enum: "info" "warning" "critical"
title
required
string
rule_code
string or null

The alert rule that raised it, for example acute_gi, red_flag_symptom, moderate_side_effects.

source
required
string
Enum: "alert_rule" "ai_coach_escalation" "coach"

An alert rule on the logs, the AI Coach escalating a conversation (ai_only), or a coach.

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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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, clinical.flag_redelivered is sent.

redelivery_count
required
integer

How many times the flag has been re-delivered for a missed ack_due_at.

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.

Request samples

Content type
application/json
{
  • "on_behalf_of": {
    • "npi": "1234567893",
    • "name": "Dr. Alex Morgan, NP"
    }
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ],
  • "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": {
      }
    },
  • "resolution": null,
  • "created_at": "2026-10-26T14:05:00Z",
  • "updated_at": "2026-10-26T14:20:00Z"
}

Resolve a flag

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

Authorizations:
clinicalClient
path Parameters
flag_id
required
string <uuid>
Example: 0190f5d8-4b2c-7d5e-9f10-b1c2d3e4f5a6
header Parameters
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).

Request Body schema: application/json
required
required
Prescriber (object) or Prescriber (object) (Prescriber)

The individual prescriber acting through a clinical client. name and at least one of npi or prescriber_id are required. Stored on the flag with the client that sent it.

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.

Responses

Response Headers
X-Request-Id
string
Example: "req_019a3b2c-4d5e-7f60-8a1b-2c3d4e5f6a7b"

Unique id of this request. Echoes a valid X-Request-Id you sent, otherwise generated. Also in every problem body.

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"

true when this response is a replay of an earlier request with the same Idempotency-Key.

Response Schema: application/json
id
required
string <uuid> (Uuid)
status
required
string (ClinicalFlagStatus)
Enum: "open" "acknowledged" "resolved" "dismissed"

open until a prescriber acknowledges it; acknowledged until resolved. dismissed flags were closed by TeraRx as raised in error and never change risk.

severity
required
string (Severity)
Enum: "info" "warning" "critical"
title
required
string
rule_code
string or null

The alert rule that raised it, for example acute_gi, red_flag_symptom, moderate_side_effects.

source
required
string
Enum: "alert_rule" "ai_coach_escalation" "coach"

An alert rule on the logs, the AI Coach escalating a conversation (ai_only), or a coach.

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"
  • ai_only: the AI Coach only. Escalations go to the brand's prescriber as clinical flags.
  • ai_health_fitness: the AI Coach plus a health coach (nutrition) and a fitness coach.
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, clinical.flag_redelivered is sent.

redelivery_count
required
integer

How many times the flag has been re-delivered for a missed ack_due_at.

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.

Request samples

Content type
application/json
{
  • "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"
}

Response samples

Content type
application/json
{
  • "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": [
    • {
      }
    ],
  • "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": {
      }
    },
  • "resolution": {
    • "at": "2026-10-26T15:10:00Z",
    • "by": {
      },
    • "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"
}

Webhooks

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.

Envelope

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

Verifying the signature

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:

  1. Read the raw request body as bytes. Do not parse and re-serialize the JSON first; any change in whitespace breaks the signature.
  2. Read TeraRx-Timestamp. Reject the delivery if it is more than 5 minutes from your clock (replay protection).
  3. Build the signed payload: the timestamp, a period, then the raw body: <timestamp>.<raw body>.
  4. Compute HMAC-SHA256(secret, signed_payload) as lowercase hex, using your webhook secret.
  5. Split TeraRx-Signature on commas. Accept the delivery if any v1= value matches, compared in constant time.
  6. Dedupe on 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.
  7. Answer 2xx quickly, then process. Do the work after you respond, or queue it.

Worked example

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.

Node

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);
  });
}

Python

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(",")
    )

Secret rotation

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.

Delivery and retries

  • Answer with any 2xx within 10 seconds. Anything else, or no answer in time, is a failed attempt.
  • Failed attempts are retried with exponential backoff (about 30 s, 2 min, 10 min, 30 min, then hourly) for 24 hours. Each attempt is signed again with a fresh timestamp; the event id stays the same.
  • After 24 hours the event is dead-lettered. Nothing is lost: replay it with GET /v1/events?since=<last event id you processed>, which keeps 30 days of events in order.
  • Deliveries are not guaranteed to arrive in order. Use created_at, or fetch the current state through the API, when order matters.

Clinical flag redelivery

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.

enrollment.created Webhook

A new enrollment, after POST /v1/enrollments.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

enrollment.stage_changed Webhook

The enrollment moved along created → awaiting_assignment → assigned → onboarding_pending → onboarding_completed → protocols_active.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

enrollment.paused Webhook

The enrollment was paused. Paused days are not billed.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

enrollment.resumed Webhook

The enrollment is active again.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

enrollment.cancelled Webhook

The enrollment ended. The retention period for the patient's records starts.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

care_team.assigned Webhook

A coach was assigned to a care role (coached plan). Coach identity is not sent.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

care_team.changed Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

care_team.capacity_warning Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

care_team.feedback_due Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

form.requested Webhook

A form was sent to the patient. Prompt them in your app; they fill it in with GET /v1/patient/forms/{form_assignment_id}.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

onboarding.completed Webhook

Every blocking onboarding form is done. Protocol drafting starts.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

protocol.published Webhook

The first version of a protocol type is live. The patient reads it with GET /v1/patient/protocols/{protocol_type}.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

protocol.updated Webhook

A new version replaced the active protocol of a type (for example after a dose change).

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

message.created Webhook

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").

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

clinical.flag_raised Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

clinical.flag_redelivered Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

clinical.flag_acknowledged Webhook

A prescriber acknowledged the flag. Re-delivery stops.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

clinical.flag_resolved Webhook

A prescriber resolved the flag and set the enrollment's risk level to new_risk_level.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

patient.disengaged Webhook

No logs or messages for your brand's disengaged_after_days (7 by default). A nudge from your app may help.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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
    }
}

patient.milestone_reached Webhook

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.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}

reminder.due Webhook

A reminder is due. Send the notification in your own words, with no health details in the push text.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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
    }
}

data_request.completed Webhook

A data request finished. For an export, get a download link with POST /v1/data-requests/{data_request_id}/export-link.

Authorizations:
webhookSignature
header Parameters
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

v1=<hex> where hex is HMAC-SHA256 over <TeraRx-Timestamp>.<raw body> with your webhook secret. During a secret rotation it carries two signatures, v1=<new>,v1=<old>; accept the delivery if any one matches.

TeraRx-Event-Id
required
string <uuid>
Example: 0190f5d8-4b2d-7e6f-8a21-c2d3e4f5a6b7

The event id, the same as id in the body. Retries of one event carry the same id; store it and ignore repeats.

Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "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"
    }
}