Quickstart: from a reading to a routed follow-up
Create a program with one threshold rule, post a synthetic cuff reading through the device webhook, and watch the rule fire and attention rise - on the sandbox, no device required.
In about fifteen minutes you will create a program with one blood-pressure rule, enroll a synthetic patient, post one cuff reading through the same webhook a cellular device uses, and read back the rule execution and the attention it raised. Everything runs on your sandbox with synthetic data. No hardware, no PHI.
Prerequisites
Set the values once so the steps below can be pasted:
export ANYBIO_KEY="sk_sandbox_..." # Write scope
export PROJECT_KEY="proj_..."
export WEBHOOK_URL="https://api.anybio.io/webhooks/<provider>/$PROJECT_KEY"
export WEBHOOK_SECRET="Bearer <the value you saved in the dashboard>"
export API="https://api.anybio.io/api/v1"Your first routed reading
Create the program and its rule
A program is an episode profile. The spec below requires the blood_pressure signal (so cuff readings link to the episode), opens an episode at enrollment, and carries no agents yet. Activate it, then attach one rule: when a linked reading has systolic >= 160 OR diastolic >= 100, add 45 attention points with a six-hour half-life.
# 1a. Create the program (201 Created; keep "id" and "slug")
curl -s -X POST "$API/episode-profiles" \
-H "Authorization: Bearer $ANYBIO_KEY" -H "Content-Type: application/json" \
-d '{
"slug": "bp-between-visits",
"name": "Blood pressure between visits",
"category": "PROGRAM",
"lifecycle_mode": "AUTO_ENSURE_ACTIVE",
"auto_start_trigger": "ENROLLMENT",
"spec": {
"required_signals": ["blood_pressure"],
"detection": { "start_condition": "true" },
"thresholds": { "systolic_high": 160, "diastolic_high": 100 }
}
}'
# 1b. Activate it (a draft program does not accept enrollment)
curl -s -X POST "$API/episode-profiles/$PROFILE_ID/activate" \
-H "Authorization: Bearer $ANYBIO_KEY"
# 1c. Attach one threshold rule scoped to this program (201 Created; keep "id")
curl -s -X POST "$API/policies" \
-H "Authorization: Bearer $ANYBIO_KEY" -H "Content-Type: application/json" \
-d '{
"episode_profile_id": "'"$PROFILE_ID"'",
"slug": "bp-high-reading",
"name": "High cuff reading",
"status": "active",
"trigger_type": "observation_create",
"trigger_config": { "biosignal_ids": [3] },
"condition_expression": "systolic >= 160 OR diastolic >= 100",
"actions": [
{
"type": "contribute_attention",
"points": 45,
"signals": ["blood_pressure"],
"reason": "Cuff reading above the program threshold",
"half_life_hours": 6
}
],
"cooldown_seconds": 3600
}'Biosignal 3 is the blood-pressure panel. A panel reading has no single value; the engine exposes each component as a signal named after it, which is why the condition reads systolic and diastolic. A rule created without "status": "active" is saved as a draft and never fires.
Enroll a synthetic patient
Create a patient in the same project as the webhook, then enroll them in the program. Enrollment opens the episode that readings will link to. The identifier you choose is the value the webhook will carry as patient_id.
# 2a. Create the patient (201 Created; keep "id")
curl -s -X POST "$API/xusers" \
-H "Authorization: Bearer $ANYBIO_KEY" -H "Content-Type: application/json" \
-d '{ "identifier": "synthetic-0001", "name": "Synthetic Patient 0001", "project_key": "'"$PROJECT_KEY"'" }'
# 2b. Enroll (keep "episode_id")
curl -s -X POST "$API/episode-profiles/bp-between-visits/enroll" \
-H "Authorization: Bearer $ANYBIO_KEY" -H "Content-Type: application/json" \
-d '{ "xuser_id": "'"$XUSER_ID"'" }'The enroll response carries episode_id, profile_slug, status and xuser_id. Enrollment is refused with 400 when the program is not active.
Post a cuff reading and watch it route
Send one reading to the project's device webhook. This is the exact path a cellular cuff takes: the receiver checks the shared secret, resolves the patient by patient_id, writes a blood-pressure panel observation, links it to the active episode that requires blood_pressure, and evaluates every active observation_create rule scoped to that episode's program. Then read the rule's execution record and the episode's attention.
# 3a. Post the reading (200 OK; the body is empty)
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$WEBHOOK_URL" \
-H "Authorization: $WEBHOOK_SECRET" -H "Content-Type: application/json" \
-d '{
"metric": "blood_pressure",
"patient_id": "synthetic-0001",
"value_1": "172.00",
"value_2": "104.00",
"timestamp": "2026-09-16T14:05:00Z",
"hwi_device_id": "synthetic-cuff-01"
}'
# 3b. The rule's execution record
curl -s "$API/policies/$POLICY_ID/executions?episode_id=$EPISODE_ID" \
-H "Authorization: Bearer $ANYBIO_KEY"
# 3c. The episode's attention breakdown
curl -s "$API/episodes/$EPISODE_ID/attention" \
-H "Authorization: Bearer $ANYBIO_KEY"Success looks like this. The executions list has one entry with condition_result: true and a contribute_attention result:
{
"executions": [
{
"policy_id": "…",
"episode_id": "…",
"trigger_type": "observation_create",
"condition_result": true,
"actions_executed": [
{ "action_type": "contribute_attention", "success": true,
"result": { "points": 45, "attention_level": 45, "signals": ["blood_pressure"] } }
],
"actions_failed": null,
"executed_at": "2026-09-16T14:05:02Z"
}
]
}and the attention breakdown reads:
{
"level": 45,
"level_label": "Concerning",
"floor": 0,
"base_sum": 45.0,
"correlation_multiplier": 1.0,
"active_contributions": 1,
"top_reason": "Cuff reading above the program threshold",
"contributions": [
{ "source_name": "bp-high-reading", "original_points": 45, "current_value": 45.0,
"signals": ["blood_pressure"], "half_life_hours": 6.0, "reason": "Cuff reading above the program threshold" }
],
"config_source": "profile:bp-between-visits"
}In the dashboard, open Episodes: the synthetic patient's episode now ranks near the top, labelled Concerning. Post a second reading with a later timestamp after the cooldown and the contribution stacks; wait, and current_value decays with the six-hour half-life.
If your first request fails
| What you see | Likely cause | Fix |
|---|---|---|
401 from /api/v1 | Missing or mistyped API key | Re-copy the key from the dashboard; send it as Authorization: Bearer <key> |
403 from /api/v1 | Key scope too low, or the patient is not in your organization | Program, rule, patient and enrollment writes need Write scope |
402 from a write | The sandbox trial clock on this organization has expired | Sandbox is capped, never invoiced; contact us to extend or move to production |
400 creating the program | An enum string or the spec was rejected | category, lifecycle_mode and auto_start_trigger are upper-case; spec.detection.start_condition is required |
409 creating the program | A program with that slug already exists | Pick another slug, or reuse the existing one |
400 enrolling | Program not active, or xuser_id missing | Call POST /api/v1/episode-profiles/{id}/activate first |
404 from the webhook | Unknown project key, or webhook delivery disabled for the project | Check the project key in the URL and the Webhook enabled switch |
401 from the webhook | Shared secret mismatch | The header must equal the saved value exactly, scheme included |
200 from the webhook but no execution | Reading did not link to an episode, or the rule did not match | The episode must be active and its program must list blood_pressure in required_signals; the rule must be active with biosignal_ids: [3]; a repeated timestamp is deduplicated |
Execution shows condition_result: false | The reading is below the threshold | value_1 is systolic and value_2 is diastolic |
429 | Per-session rate limit | Back off and retry |
From here to the interview and the review queue
The rule you wrote raises attention. The same trigger can open an SMS symptom interview and, when the interview surfaces something, put an item in the care team's queue. Those steps need things this guide did not set up, and each one fails closed rather than sending a message it should not:
- A symptom protocol on the program (
spec.symptom_protocol): the clinician-authored questions, red-flag rules, routing, timing, the trigger expression and the AI-identity disclosure. Saving the program materializes the trigger as anobservation_createrule whose action isstart_symptom_session. See symptom agent and the gate. - An SMS sender for your organization and environment - dashboard only, under Settings, Notifications.
- An active communication policy - dashboard under Settings, Compliance Governance, or
PATCH /api/v1/compliance/policies/active. Without one, the gate withholds every patient-facing message. - A phone number on file (
PUT /api/v1/xusers/{xuser_id}/phone) and SMS consent open for that number - consent is only ever granted by the patient's own reply; see messaging and consent.
GET /api/v1/episode-profiles/{id}/symptom-readiness walks this ladder for a program and names what is missing. When everything is in place, a closed interview becomes an agent run in the dashboard's Review Queue, its attention contribution moves the episode up the list, and a case automation cannot continue becomes a care hand-off with an owner and a due time.
What's next
- Policy reference - every trigger, action and the attention formula
- Device ingest - the webhook payload in full, image ingest, provenance fields
- Export - send the episode onward as FHIR or HL7 v2
- API reference - the verified route list
Concepts
The eight nouns of the response loop - patient, episode, program, rule, attention, hold, hand-off, record - and the verbs we use with them.
Policy reference
The rule schema, the condition grammar, every policy action, and the attention formula, bands and defaults exactly as configured in code.