# Uphealth Signal — agents.md

> The single build-time file for a coding agent. Read this top-to-bottom and you
> can scaffold a working Signal integration in one pass: auth, a first cue, the
> daily cue loop, the response shape you render, and the modes and gates for
> going live. If anything here disagrees with the live OpenAPI spec
> (https://uphealth.us/openapi/signal-v1.json), the spec wins. Verified against
> Signal API v1 on 2026-07-22.

Signal is a cued-message API for patient sequences. You **create a stream** (one
patient's sequence), then call **`/cue`** with the patient's last response and get
back the next-right message — chosen server-side by Uphealth's sequencing engine
from a corpus of federally-sourced health content — plus a legal `_meta` block.
The first cue returns synchronously on create.

---

## Before you write code

1. Fetch and skim, in this order:
   - This file — the build recipe.
   - https://uphealth.us/llms.txt — the short machine index of every endpoint.
   - https://uphealth.us/openapi/signal-v1.json — the authoritative request/response contract (OpenAPI 3.1). A `signal-v1.yaml` twin is served alongside it if a fetcher trips on the JSON.
2. **Stop and ask the human before adding any dependency.** The whole client
   below is stdlib-only in Node 18+, Python 3 + requests, or Ruby 3.
3. **Never hard-code an API key.** It lives in an environment variable, server-side, and out of logs.
4. **You cannot mint a key.** The human pastes a free sandbox key
   (`up_sandbox_…`) from https://uphealth.us/signup. Do not create accounts.

---

## Auth and base URL

- **Base URL:** `https://api.uphealth.us/v1/signal`
- **Auth:** `Authorization: Bearer $UPHEALTH_KEY` on every call.
- **Key:** a Discovery sandbox key `up_sandbox_…` (free, self-serve, shown once at
  signup). Sandbox runs Display mode against a curated five-topic subset of the
  federal corpus — no PHI, no BAA.

```bash
export UPHEALTH_KEY="up_sandbox_…"      # never hard-code; read from the env
# BASE = https://api.uphealth.us/v1/signal
```

Build a fetch wrapper that sets the Bearer header, parses JSON, and retries
`5xx`/`429` with backoff. Keep the key out of client-side code and out of logs.

---

## Step 1 — Create a stream (the first cue is synchronous)

POST a `template_id` to open a stream. The response carries the **first cue in the
same call** — there is no second request for the opening message. Any live
template id is accepted; a sandbox stream always plays the curated corpus, so
start with `general_wellness_daily`.

```bash
curl -X POST https://api.uphealth.us/v1/signal/streams \
  -H "Authorization: Bearer $UPHEALTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template_id": "general_wellness_daily" }'
```

**201 Created**

```json
{
  "stream_id": 1,
  "state": "awaiting_feedback",
  "first_cue": {
    "cue_event_id": 9,
    "message_id": 1,
    "message_class": "sandbox_message",
    "kind": "fact",
    "body": "Heart disease is the leading cause of death in the United States for both men and women.",
    "source_url": "https://www.cdc.gov/heart-disease/data-research/facts-stats/index.html",
    "source_quote": "Heart disease is the leading cause of death for men, women, and people of most racial and ethnic groups.",
    "receptivity": 0.5,
    "safety": { "verdict": "included", "reason": "unrestricted", "matched_tags": [], "gating_tags": [], "excluded_tags": [] },
    "arc": null,
    "day_of_stream": 1,
    "cued_at": "2026-05-30T01:24:34Z"
  },
  "next_cue_eligible_at": null,
  "_meta": {
    "api_version": "1.0",
    "no_medical_advice": true,
    "not_samd": true,
    "license_attribution": "Uphealth content adapted from federally-sourced public-domain materials under 17 USC §105.",
    "delivery_mode": "display",
    "sequencing_ip_notice": "This response includes Uphealth's proprietary cueing decision. License covers receipt + display; not reverse-engineering or training data extraction."
  }
}
```

Optional create fields (see the spec): `attributes`, `audience_tags`,
`delivery_mode`, `feedback_timeout_days`. **`idempotency_key` is a `/cue` field
only — there is none on create.** Sandbox ignores PHI-bearing context by design.

---

## Step 2 — The cue loop (one call per patient per day)

POST the patient's response to the message you last showed. `response_action` is
one of nine engagement archetypes; `idempotency_key` (a UUID) lets you retry
safely.

```bash
curl -X POST https://api.uphealth.us/v1/signal/streams/1/cue \
  -H "Authorization: Bearer $UPHEALTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "feedback": { "response_action": "did_it",
        "idempotency_key": "550e8400-e29b-41d4-a716-446655440000" } }'
```

**The nine `response_action` values:** `did_it` · `already_do` · `will_try` ·
`check_it` · `new_to_me` · `needed_this` · `know_it` · `acknowledge` ·
`no_response`. Map your UI's engagement buttons onto these; use `no_response`
when the patient didn't reply.

**200 OK** returns the next message under `next_cue` (same shape as `first_cue`),
plus `idempotent` (bool) and `normalized_action`.

**The one structural rule:** call `/cue` with **no `feedback` block** and you get
**`409 feedback_required`**. This is not a policy — the engine cannot pick the
next-right message without the patient's signal on the last one.

---

## The response object — what you render

Every cue (first or next) carries these. Render label + body + source on each:

| Field | Use |
| --- | --- |
| `kind` | `fact` · `list` · `tip` — pick your render label (Know it / Did it / Check it). |
| `body` | The message text to show or send. |
| `source_url` | The federal source (CDC / NIH / HHS / USPSTF / Dietary Guidelines). Show it — provenance is the trust. |
| `source_quote` | The exact sentence in that source the message rests on. |
| `receptivity` | Advisory float 0.00–1.00 (0.50 on a new stream). Read it or ignore it. |
| `safety` | Audience-safety verdict, always present, PHI-free. |
| `arc`, `day_of_stream` | Curated-arc position on paid spine-backed streams (`arc` is `null` in sandbox); advisory wall-clock day. |
| `state` | `awaiting_feedback` — the next `/cue` needs the patient's response first. |
| `_meta` | Legal block on every response — six fields: `api_version`, `no_medical_advice`, `not_samd`, `license_attribution`, `delivery_mode`, `sequencing_ip_notice`. Keep it attached. |

---

## Modes and going live

- **Display mode** (sandbox + default): you render the message in your own app.
  Free, no PHI, no BAA, self-serve. Sandbox is capped at **50 cue calls per
  calendar month (UTC)**; the 51st returns **`429 over_quota`** with an
  `upgrade_url` — terminal, do not retry (distinct from a rate-limit `429`, which
  you back off and retry). Only `create` + a fresh `cue` spend quota; reads,
  idempotent replays, and 4xx rejects are free.
- **Deliver mode:** Uphealth sends on your behalf. **BAA-gated at the PMPM tier
  and above.** PHI never flows to the email provider (notify-and-link).
- **Sandbox → live:** same shape; a live key lifts the sandbox corpus and cap and
  unlocks real templates. See https://uphealth.us/docs/live-promote.

---

## What v1 ships (and what it doesn't)

- **Six endpoints, five reference pages:** `POST /streams`, `GET /streams/{id}`,
  `POST /streams/{id}/cue`, `GET /sandbox-topics`, `GET /templates`,
  `GET /templates/{id}`.
- **No `enroll` endpoint** (enrollment is implicit in create) and **no `close`
  endpoint**.
- **Sandbox = Display mode, five curated federal topics, 50 cues/month.** No PHI.
- Every message is federally sourced and carries `source_url` + `source_quote` —
  there is no free-text or generated-message path.

---

## The corpus boundary (important)

These docs, the Signal API, and the OpenAPI spec are **meant to be built
against** — read, index, and integrate them freely. The Uphealth **daily-message
library** (the Facts / Lists / Tips delivered to members on uphealth.me) is a
**separate, unpublished corpus** — it is NOT published here and NOT available for
training or indexing. That boundary is intentional; the member side is described
at https://uphealth.me/llms.txt.

---

## Related surfaces

| Surface | URL | For |
| --- | --- | --- |
| Short agent index | https://uphealth.us/llms.txt | endpoints + quotas, at a glance |
| Full docs (machine) | https://uphealth.us/llms-full.txt | every page, one file |
| OpenAPI 3.1 (JSON) | https://uphealth.us/openapi/signal-v1.json | the authoritative contract |
| OpenAPI 3.1 (YAML) | https://uphealth.us/openapi/signal-v1.yaml | same, YAML twin |
| Postman collection | https://uphealth.us/openapi/uphealth-signal.postman_collection.json | click-to-run |
| MCP server (runtime) | https://mcp.uphealth.us/mcp | for an assistant to *use* Signal, not build against it |
| Human docs | https://uphealth.us/docs | any page is also Markdown — append `.md` |

Every capability here is also reachable in plain language — see
https://uphealth.us/signal/evaluate (have your assistant evaluate Signal for you).
