This walkthrough takes you from an empty tenant to a completed async consult: provision a patient and a physician, open a consult, exchange messages, and resolve it. Every request shown is real — the routes, bodies, and responses match the typed contract ( Endpoints) exactly.
pp_live_… in production, pp_test_… elsewhere) and
your assigned API host. Keys are server-side secrets — see the
Authentication guide.export NATZAR_API="https://<your-assigned-api-host>"
export NATZAR_KEY="pp_test_..."
All routes live under /v1 and every request carries
Authorization: Bearer $NATZAR_KEY.
POST /v1/patients is an idempotent upsert keyed on your externalId —
run it as many times as you like; repeats update the mutable fields, never
duplicate. phone must be E.164 and is the patient's identity key
(immutable after creation).
curl -s -X POST "$NATZAR_API/v1/patients" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "pt-1001",
"phone": "+15551234567",
"email": "jane.doe@example.com",
"givenName": "Jane",
"familyName": "Doe",
"birthdate": "1990-04-17",
"sex": "female",
"lang": "en_US"
}'
Response (200):
{
"patient": {
"id": "aB3xK9mQpZ4r",
"externalId": "pt-1001",
"phone": "+15551234567",
"email": "jane.doe@example.com",
"givenName": "Jane",
"familyName": "Doe",
"birthdate": "1990-04-17",
"sex": "female",
"lang": "en_US",
"messageChannel": "partner",
"createdAt": "2026-08-13T09:30:00.000Z",
"updatedAt": "2026-08-13T09:30:00.000Z"
},
"created": true
}
Note messageChannel: "partner": API-provisioned patients are never
contacted over WhatsApp/SMS — every message reaches them through your
integration (webhooks, the messages endpoint, or the embed widgets).
You can address this patient by our id or your externalId
interchangeably wherever a consult is created.
POST /v1/physicians creates the physician profile plus a backing portal
account keyed by email. An identical repeat (same externalId + same
email) is idempotent — 200, names refreshed — so retries are safe; repeating
an externalId with a different email yields 409 external_id_conflict,
and a new externalId with an already-registered email yields
409 email_in_use. By default no email is sent; pass
sendPortalInvite: true (or call POST /v1/physicians/{id}/portal-invite
later) if the physician should sign in to the Natzar portal.
curl -s -X POST "$NATZAR_API/v1/physicians" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "dr-42",
"email": "s.chen@partner-clinic.example",
"givenName": "Sarah",
"familyName": "Chen",
"sendPortalInvite": false
}'
The response's physician.id is the value you will pass as physicianId
on reply/claim/takeover/resolve calls. New physicians default to
asyncAvailable: true — they participate in automatic assignment
immediately; toggle via POST /v1/physicians/{id}/availability.
consent declares who collects the patient's consent:
"collected" — you already collected it in your own UX; the consult is
queued/assigned immediately."embed" — the <natzar-async> widget collects it; the create response
carries a sessionToken (see the Embed guide).curl -s -X POST "$NATZAR_API/v1/async-consults" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalPatientId": "pt-1001",
"context": "34yo female, 3 days of sore throat and low-grade fever, no strep exposure known.",
"consent": "collected"
}'
Response (200, physician capacity available):
{
"consult": {
"id": "kQ7wN2pT9xLm",
"patientId": "aB3xK9mQpZ4r",
"externalPatientId": "pt-1001",
"status": "active",
"context": "34yo female, 3 days of sore throat and low-grade fever, no strep exposure known.",
"assignedPhysicianId": "e5f0c8aa-…",
"assignedPhysicianExternalId": "dr-42",
"assignedPhysicianName": "Dr. Sarah Chen",
"rateable": false,
"origin": "partner",
"createdAt": "2026-08-13T09:31:00.000Z",
"sentAt": "2026-08-13T09:31:00.000Z",
"lastActivityAt": "2026-08-13T09:31:00.000Z"
}
}
If no physician had capacity, status is "queued" — assignment happens
automatically the moment one becomes available (you'll get an
async_consult.assigned webhook). One open thread per patient is an
invariant: creating a second consult while one is open yields
409 has_open_thread with the existing consult's id in error.details.
Your patient UI posts patient-authored messages. The response is a 202 — the message is validated and enqueued into the per-patient FIFO pipeline, and will appear on the transcript in order (see Errors & pagination for the full 202 semantics).
curl -s -X POST "$NATZAR_API/v1/async-consults/kQ7wN2pT9xLm/messages" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "The fever is up to 38.5 tonight, should I be worried?"}'
{"accepted": true, "messageId": "mA1bC2dE3fG4"}
messageId is the id the message will carry on the transcript and in the
async_consult.message webhook.
If your clinicians work in your own UI (headless integration), post their
replies with the assigned physician's id. physicianId must be the
consult's current assignee — otherwise 409 not_assigned (assignment
can move via SLA reassignment; re-read the consult).
curl -s -X POST "$NATZAR_API/v1/async-consults/kQ7wN2pT9xLm/replies" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{
"physicianId": "e5f0c8aa-…",
"text": "38.5 with a sore throat is usually viral. Alternate acetaminophen and fluids tonight; if it passes 39.5 or you develop trouble swallowing, message me right away."
}'
Also 202 with a messageId. (Physicians who use the Natzar portal
instead reply there — you don't call this endpoint for them; their replies
still show up in the transcript and webhooks identically.)
curl -s "$NATZAR_API/v1/async-consults/kQ7wN2pT9xLm/messages?limit=50" \
-H "Authorization: Bearer $NATZAR_KEY"
Returns a page of messages, oldest first, including system lifecycle
notices (assignment, warnings, close notices) — the transcript is complete.
Lifecycle-generated messages carry a classification like
"async:assigned" so a headless UI can render semantic chips instead of
the localized body text. Attachment urls in the response are short-lived
presigned links — fetch promptly, never persist the URL.
The assigned physician marks the consult resolved; the patient gets a
bounded window to reply (a reply reopens the thread to active, silence
closes it as accepted — you'll see async_consult.closed with
closedReason: "accept_timeout").
curl -s -X POST "$NATZAR_API/v1/async-consults/kQ7wN2pT9xLm/resolve" \
-H "Authorization: Bearer $NATZAR_KEY" \
-H "Content-Type: application/json" \
-d '{"physicianId": "e5f0c8aa-…", "note": "Supportive care; message again if symptoms worsen."}'
The response's consult.status is "resolve_requested".
<natzar-async> / <natzar-telehealth>: Embed.POST /v1/telehealth-consults works just like step 3
(minus consent); the patient joins via the embed, the physician takes
the call in the Natzar portal, and a recording + transcript arrive via
telehealth.recording_ready.POST /v1/attachments/upload-urls,
PUT the bytes, then reference the returned stagingKey in a message or
reply.Everything above books nothing — the patient joins a queue and sees whoever is free. To offer an appointment at a time they choose, with a named physician, see Scheduled consultations.