Flows API
The public runtime for a published funnel — fetch a patient-safe definition, open a session, save steps, submit into intake, and post PHI-free events — plus the authenticated AI-generation surface.
Flows is neolife's funnel builder: a clinic authors a quiz → medical intake → checkout funnel, a licensed provider approves it, and patients flow through it into the fulfillment rail. This page covers the two surfaces the API exposes: the public runtime (/v1/public/flows/*) that a hosted page or embedded flow.js calls with no user account, and the authenticated AI-generation calls (/v1/flows/ai/*) staff use to draft a flow. For the model behind it — the two planes, the builder, the lifecycle — see Flows.
All paths are relative to https://api.neolife.health/v1. Runtime calls are addressed by an embed key (emb_…), not an API key: it is public by design and only ever returns a patient-safe definition. Authenticated calls take Authorization: Bearer nk_live_… (or nk_sandbox_…).
A flow can produce a live clinical screen only after a licensed provider approves it. The runtime below serves already-published flows; the clinical plane a patient sees was signed off before publish and is fixed by the flow's clinical-plane hash. AI drafts, a licensed provider signs.
Get a flow definition#
GET /public/flows/:embedKey
The patient-safe definition of a published flow: presentation copy, theme tokens, step order, and the question shapes for each step. It carries no internal rule ids, no disqualifier logic, and no provider packet — only what a browser needs to render. This is what flow.js fetches on load and what the hosted page at /f/:embedKey is built from.
curl https://api.neolife.health/v1/public/flows/emb_abc123
Served from neolife's BAA-covered API (never from a static host). A revoked or unpublished embed key returns 404. Themes are surfaced as --nl-flow-* CSS variables so the clinic's brand tokens render without exposing anything clinical.
Open a session#
POST /public/flows/:embedKey/sessions
Start one patient's run through the flow. Returns a sessionId you carry through the remaining calls. If the flow has a running A/B experiment, the arm is assigned here — deterministic, sticky, and server-side, so the same visitor always sees the same arm and every arm resolves to the identical clinical plane.
curl -X POST https://api.neolife.health/v1/public/flows/emb_abc123/sessions \
-H "Origin: https://start.yourclinic.com" \
-H "Content-Type: application/json" \
-d '{}'
| Field | Type | Notes |
|---|---|---|
variant |
string | Optional — a resume hint. Assignment is server-side; a client cannot pick its own arm. |
Outside sandbox, Origin (or Referer) must match an allowlisted origin for the flow — a mismatch is rejected. The response carries the sessionId and, when experiments are on, the assigned arm.
Save a step#
POST /public/flows/:embedKey/sessions/:id/steps
Persist the answers for one step as the patient advances — a partial save, so a dropped connection or a back-button never loses progress. Presentation-plane answers (a marketing quiz) and clinical-plane answers are both accepted here; the clinical answers are held for the intake engine and are never echoed back to the browser.
curl -X POST https://api.neolife.health/v1/public/flows/emb_abc123/sessions/fses_123/steps \
-H "Origin: https://start.yourclinic.com" \
-H "Content-Type: application/json" \
-d '{
"stepId": "eligibility",
"answers": { "age": 41, "pregnant": false, "bmi": 31 }
}'
| Field | Type | Notes |
|---|---|---|
stepId |
string | Which step these answers belong to. |
answers |
object | Answer map keyed by question id, for this step only. |
Returns the saved-step acknowledgement. It does not compute a verdict — that happens at submit.
Submit a session#
POST /public/flows/:embedKey/sessions/:id/submit
Finish the run. The accumulated clinical answers are handed to the deterministic intake engine exactly as an embed intake submit would be — same certified questionnaire, same rules. An eligible run produces a certificate, lands in the provider review queue, and (once a provider approves) routes to the pharmacy. neolife scores eligibility; a licensed provider still makes the call.
curl -X POST https://api.neolife.health/v1/public/flows/emb_abc123/sessions/fses_123/submit \
-H "Origin: https://start.yourclinic.com" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1b0c2a-9d6e-4a51-8b2f-1c7e5a9d0e42" \
-d '{
"patient": { "name": "Jane Doe", "email": "[email protected]" }
}'
| Field | Type | Notes |
|---|---|---|
patient |
object | { name, email } for the lead. |
The response is deliberately minimal — a patient-safe verdict, and, when the flow ends in checkout, a handoff:
{
"sessionId": "fses_123",
"verdict": "eligible",
"certificate": "cert_...",
"checkoutUrl": "https://shop.yourclinic.com/checkout?nl_cert=cert_...",
"sandbox": false,
"message": "Thanks — your answers are with a licensed provider for review. You'll hear back shortly."
}
| Field | Meaning |
|---|---|
verdict |
eligible, disqualified, or incomplete. |
certificate |
Certificate reference for an eligible run. |
checkoutUrl |
Present when the flow hands off to the clinic's store — carries the certificate token, never money. |
message |
A ready-to-render, patient-safe message. |
Checkout is a handoff, not a payment. The flow ends by redirecting to the clinic's own store, carrying the certificate token — neolife never holds patient money. Rx can't run on Stripe. Gate the prescription, not the cart.
checkoutUrlonly appears when theflow_checkoutfeature is enabled; it is counsel-gated.
Send an Idempotency-Key so a retried submit never creates a second submission or a duplicate lead.
Post an event#
POST /public/flows/:embedKey/sessions/:id/events
A PHI-free analytics beacon: view, step-viewed, step-completed, and drop-off signals from the browser. It carries a closed vocabulary of event names and no answers, no email, no health context — the funnel analytics are PHI-free by construction, by schema, not by policy.
curl -X POST https://api.neolife.health/v1/public/flows/emb_abc123/sessions/fses_123/events \
-H "Origin: https://start.yourclinic.com" \
-H "Content-Type: application/json" \
-d '{ "type": "step_viewed", "stepId": "eligibility" }'
| Field | Type | Notes |
|---|---|---|
type |
string | A closed-vocabulary event name (for example view, step_viewed, step_completed). |
stepId |
string | Optional — the step the event refers to. |
The beacon accepts arbitrary retries and never returns anything but an acknowledgement. Because it is PHI-free by schema, these are the only funnel signals that reach the ad-pixel destinations — and only on an eligible run, never disqualified.
Describe your program (AI generation)#
POST /flows/ai/generate
Authenticated. Staff describe the program they want in plain language; the AI matches the description to a certified questionnaire from neolife's corpus and drafts the presentation plane — landing and outcome copy, a theme, and a marketing quiz. It writes the presentation plane only. It never authors a live clinical screen: the clinical questions, disqualifiers, and consent come from the certified questionnaire it matched to, unchanged.
Requires a Flows-manage scope. Runs offline against a deterministic stub, so it behaves identically in sandbox.
curl -X POST https://api.neolife.health/v1/flows/ai/generate \
-H "Authorization: Bearer $NEOLIFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "A weekly wellness program for busy professionals, calm and clinical tone."
}'
| Field | Type | Notes |
|---|---|---|
description |
string | Plain-language description of the program. |
The response is a draft flow — copy, theme, and the matched certified questionnaire wired into the clinical plane. It is not published: a licensed provider still approves it before it can serve patients. See Flows for the builder and publish flow.
Draft a new questionnaire (freeform medical)#
POST /intake/questionnaires/ai-draft
When the corpus does not cover a product, the AI drafts a full questionnaire in the certified schema — clinical questions, disqualifiers, and consent — as a starting point. This is the one path where AI touches clinical content, so it is fenced:
- Gated by the counsel-gated
flow_ai_freeform_medicalfeature flag. - The draft is run through a deterministic validator before it can be saved — a draft that does not conform to the certified schema is rejected.
- The draft is inert until a licensed provider approves it (see below). AI never authors a live clinical screen.
curl -X POST https://api.neolife.health/v1/intake/questionnaires/ai-draft \
-H "Authorization: Bearer $NEOLIFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Eligibility screen for a topical anti-inflammatory program."
}'
| Field | Type | Notes |
|---|---|---|
description |
string | Plain-language description of the product to screen for. |
Returns a draft questionnaire in pending state. A caller without the flow_ai_freeform_medical flag gets 403.
Approve a questionnaire#
POST /intake/questionnaires/:id/approve
A licensure act. A licensed provider reviews an AI-drafted (or edited) questionnaire and approves it. On approval it becomes a usable certified tenant questionnaire — from that point it behaves like any other member of the corpus and can back a flow's clinical plane. This endpoint is provider-gated: the caller needs the intake-review scope and a verified NPI.
curl -X POST https://api.neolife.health/v1/intake/questionnaires/qst_123/approve \
-H "Authorization: Bearer $NEOLIFE_API_KEY" \
-H "Idempotency-Key: 7c2d1e0a-4b6f-4a51-9d3e-2a8c5f9b0e13" \
-d '{ "note": "Reviewed schema and clinical logic; approved for use." }'
| Field | Type | Notes |
|---|---|---|
note |
string | Optional review note. |
A caller without the review scope or without an NPI gets 403. Send an Idempotency-Key so a retried approval never double-records.
Webhooks#
A published flow, and the sessions that move through it, emit the signed, PHI-free flow.* events you can subscribe a webhook endpoint to. They carry ids and a neutral funnel label only — never answers, email, or clinical detail.
| Event | Fires when |
|---|---|
flow.published |
A flow is published (clinical-plane hash + provider approver recorded). |
flow.session.qualified |
A session reaches an eligible verdict. |
flow.session.completed |
A session finishes its run. |
flow.checkout.completed |
The clinic's store reports the handoff checkout completed. |
These are the same events a Zapier connection consumes. For delivery, signing, and reconciliation, see Webhooks & events.
Related#
- Flows — the two-plane model, the builder, experiments, and analytics.
- Intake — the deterministic engine a flow submit delegates to, and the certificate it issues.
- PHI boundary — why the runtime definition, the event beacon, and the analytics carry no PHI.
- Idempotency — why every mutation takes a key.
- API reference — base URL, versioning, errors, and rate limits.