Developers / API

The fulfillment rail, as an API.

neolife is infrastructure, not an app you log into. A REST API with a generated OpenAPI spec, typed SDK, a CLI, signed webhooks, and an MCP server — the same rail our own product runs on, open to yours. Build the whole integration against synthetic patients before a BAA is ever signed.

OpenAPI 3.1

Generated spec at /openapi.json

Sandbox

Synthetic patients only — no BAA needed to start

PHI-free

Webhooks carry ids and status, never patient data

The primitives

Everything you need to build against, and nothing you don't.

One rail, exposed six ways. Pick the surface that fits your stack — they all hit the same order pipeline, with the same rules behind them.

API keys

Sandbox and live, cleanly split

Live keys move real orders. Sandbox keys resolve only to synthetic patients, so you can build and break the whole integration before a BAA is signed. Restricted keys scope down to exactly the permissions an integration needs.

nk_live_… · nk_sandbox_… · rk_…

Node SDK

@neolifehealth/sdk — typed, zero-dependency

A typed client for orders, catalog, intake, keys, and webhook endpoints, plus verifyWebhook for signature checking. Native fetch, Node 18+, no runtime dependencies. Every mutation takes an idempotency key.

pnpm add @neolifehealth/sdk

CLI

neolife — the terminal front door

Log in, mint keys, list orders, browse the catalog, and draft orders from your shell. The headline command, trigger, fires a correctly signed test event straight at a local receiver — no relay service to stand up.

neolife trigger order.shipped

Webhooks

Signed, retried, and PHI-free by contract

Standard-Webhooks HMAC-SHA256 signatures, automatic retry with backoff, and a ±5-minute replay window. Payloads carry ids and status — never patient data — so you can point our webhooks anywhere.

webhook-signature: v1,<hmac>

Idempotency

Retries that can't double-ship

Every mutation accepts an Idempotency-Key. The API replays the first response for a repeated key and returns 409 on the same key with a different body. A double-submit is a second physical shipment — this is what stops it.

Idempotency-Key: <uuid>

MCP server

Agent-native access to the same rail

The same order pipeline, exposed over the Model Context Protocol so your agents can draft and track orders directly. A licensed provider still approves every order. Documented in full on the MCP docs page.

POST /mcp/tools/call

Start with a key

One Bearer header, and you're drafting orders.

Authenticate with a machine key and the prefix decides the world you're in. A nk_sandbox_ key resolves only to synthetic patients, so this exact call is safe to run on day zero — nothing touches a real person until you swap in a nk_live_ key under a BAA.

The Idempotency-Key makes the request safe to retry — the API replays the first response instead of creating a duplicate.

curl

# Sandbox keys resolve ONLY to synthetic patients — no BAA needed to start.
curl https://api.neolife.health/v1/orders \
  -H "Authorization: Bearer nk_sandbox_7f21a9c4e0b8…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "clinicId": "cln_8f21",
    "patientRef": "pt_4b9c",
    "items": [{ "productId": "prod_tdf30", "quantity": 1, "sig": "Apply nightly" }],
    "shipping": { "method": "standard", "to": "addr_77a2" }
  }'

# → 201 Created  { "id": "ord_19d4", "status": "draft", "approvalRequired": true }

The Node SDK

Create, approve, submit — idempotent, and webhook-verified.

@neolifehealth/sdk is a typed client with no runtime dependencies. It creates a draft, routes it for provider approval, and submits it with an idempotency key so a retried call can never double-ship. verifyWebhook does the constant-time signature check on the way back.

@neolifehealth/sdk

import { Neolife, verifyWebhook, NeolifeError } from "@neolifehealth/sdk";
import crypto from "node:crypto";

// A nk_sandbox_ key => client.mode === "sandbox" (synthetic patients only).
const client = new Neolife({ apiKey: process.env.NEOLIFE_API_KEY! });

// Create → approve (provider-gated) → submit. The Idempotency-Key makes the
// retry safe: a double-submit would be a second physical shipment.
const draft = await client.orders.create({
  clinicId: "cln_8f21",
  patientRef: "pt_4b9c",
  items: [{ productId: "prod_tdf30", quantity: 1, sig: "Apply nightly" }],
  shipping: { method: "standard", to: "addr_77a2" },
});

await client.orders.approve(draft.id);                       // a licensed provider signs
await client.orders.submit(draft.id, {                       // idempotent by key
  idempotencyKey: crypto.randomUUID(),
});

// Verify an incoming webhook — pass the RAW body, before JSON.parse.
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = verifyWebhook(req.body, req.headers, process.env.NEOLIFE_WEBHOOK_SECRET!);
    console.log(event.type, event.data);   // PHI-free: ids + status only
    res.sendStatus(204);
  } catch (err) {
    res.sendStatus(400);                     // bad signature => reject
  }
});

The CLI

Test a signed webhook without standing up a relay.

The neolife CLI is the human-in-terminal front door. It defaults to sandbox and prints a persistent mode badge on every command, so you never fire at live by accident. There is no --submit: the terminal is never a compliance backdoor.

trigger builds and signs the exact envelope the server emits, so an event fired locally verifies against the same receiver code a production event would.

neolife

# Default mode is SANDBOX. Every command prints a persistent SANDBOX / LIVE badge.
neolife login --key nk_sandbox_7f21a9c4e0b8…

neolife keys create --name "shopify-integration" --mode sandbox --type secret
neolife orders list --status draft
neolife catalog list

# Draft only — there is no --submit. A provider approves server-side.
neolife orders create --items '[{"productId":"prod_tdf30","quantity":1}]'

# Fire a correctly Standard-Webhooks-signed event at your local receiver — no relay.
neolife trigger order.shipped \
  --forward-to http://localhost:3000/webhooks \
  --secret whsec_your_signing_secret

The webhook you can point anywhere

Our webhooks never carry PHI. By contract, not by convention.

Every event payload carries ids, status, and reason codes — never patient data. That single design choice means you can route neolife webhooks to Slack, a queue, a Lambda, or a third-party tool with no BAA in the path. When you need the PHI behind an event, you hydrate it with an authenticated, BAA-covered GET — on your terms, not pushed at you.

Event catalog

intake.submission.createdintake.certificate.issuedintake.review.approvedintake.review.rejectedorder.routedorder.submittedorder.acceptedorder.shippedorder.deliveredorder.rejected

Signed to the Standard-Webhooks spec (HMAC-SHA256), retried automatically, and rejected outside a ±5-minute replay window.

The reference

A generated spec, and docs you can call.

The API is described by an OpenAPI 3.1 document generated from the server itself, so the docs never drift from the code. Pull the spec into your own tooling, or use the interactive reference to try a call against sandbox in the browser.

OpenAPI 3.1/openapi.json
A generated, machine-readable spec you can pull into your own tooling, generate clients from, or diff on every release.
Interactive docs/docs
The full API reference — every endpoint, request, and response — rendered from the same spec and callable in-browser against sandbox.
Bearer authAuthorization
One header. Sent as Authorization: Bearer nk_live_… or nk_sandbox_… — the key prefix decides whether you touch real patients or synthetic ones.
Request idsX-Request-Id
Every response carries a req_… id. Errors surface it too, so a support conversation starts from the exact call that failed.

Build the whole thing on synthetic patients first.

Read the reference, wire up a sandbox key, and get an order from draft to shipped — before a single real patient is in the loop.