API

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. checkoutUrl only appears when the flow_checkout feature 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_medical feature 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.