AnyBio

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 seeLikely causeFix
401 from /api/v1Missing or mistyped API keyRe-copy the key from the dashboard; send it as Authorization: Bearer <key>
403 from /api/v1Key scope too low, or the patient is not in your organizationProgram, rule, patient and enrollment writes need Write scope
402 from a writeThe sandbox trial clock on this organization has expiredSandbox is capped, never invoiced; contact us to extend or move to production
400 creating the programAn enum string or the spec was rejectedcategory, lifecycle_mode and auto_start_trigger are upper-case; spec.detection.start_condition is required
409 creating the programA program with that slug already existsPick another slug, or reuse the existing one
400 enrollingProgram not active, or xuser_id missingCall POST /api/v1/episode-profiles/{id}/activate first
404 from the webhookUnknown project key, or webhook delivery disabled for the projectCheck the project key in the URL and the Webhook enabled switch
401 from the webhookShared secret mismatchThe header must equal the saved value exactly, scheme included
200 from the webhook but no executionReading did not link to an episode, or the rule did not matchThe 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: falseThe reading is below the thresholdvalue_1 is systolic and value_2 is diastolic
429Per-session rate limitBack 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 an observation_create rule whose action is start_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

On this page