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+jsonforPATCH). 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).
unitson the patient is a display preference only. - Field names are
snake_case. Absent andnullboth mean “no value”.
Request ids
Every response has anX-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 contact TeraRx.
Errors
Every 4xx and 5xx response is RFC 9457 problem details (application/problem+json):
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.
Each operation lists the codes specific to it.
Idempotency
EveryPOST that creates something requires an Idempotency-Key header (a UUID is a good choice); without it the request is refused with 400 idempotency_key_missing. Other POST actions accept one.
- 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.
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) and, for patient tokens, per patient. Every response carries:
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 (thex-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 only. 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 withis_red_flag: true, or an AI Coach reply with show_emergency_guidance: true, show it before anything else.
Environments
Each environment has its own clients, keys and webhook secret.

