Skip to content

Create Lead

Raises a lead — for an existing patient, or for a new one created in the same call — and answers as soon as its case exists.

Endpoint

POST /v2/leads

Request Body

Parameter Type Required Description
patient_id string No An existing patient to raise the lead for. Leave it out to create the patient instead, with the fields below.
first_name string Conditional New patient only — required without patient_id.
last_name string Conditional New patient only — required without patient_id.
date_of_birth string (date) Conditional New patient only — required without patient_id. YYYY-MM-DD.
clinic_ids array of string Conditional New patient only — required without patient_id. Only the first is used: it becomes the patient's clinic, and the lead's.
email string No New patient only.
mobile string No New patient only.
middle_name string No New patient only.
suffix string No New patient only.
gender string No New patient only — as on Create Patient. One of MALE, FEMALE, OTHER.
address object No New patient only. See Address.
preferred_language string No New patient only — as on Create Patient.
clinic_id string No With patient_id only — rejected without it. The clinic the lead belongs to. Defaults to the patient's clinic; required for a token limited to specific clinics.
title string No With patient_id only — rejected without it. A title for the case. Generated when omitted.
speciality string No With patient_id only — rejected without it. The case's speciality. Often not known yet when a lead is raised.
complaint string No The patient's presenting complaint, in their words — with either shape.
lead_source string No Where the lead came from. MANUAL when omitted. One of MANUAL, REFERRAL, WEBSITE, PHONE, FAX, PATIENT_PORTAL.

Address

Parameter Type Required Description
line1 string or null No
line2 string or null No
city string or null No
state string or null No
postal_code string or null No
country string or null No

Request

For an existing patient:

curl --location '{base_url}/v2/leads' \
  --header 'Authorization: Bearer JWT_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
  "patient_id": "63120",
  "clinic_id": "5831",
  "title": "L Knee Post-Op",
  "complaint": "Pain in the left knee since surgery"
}'

For a new patient — the body of Create Patient, plus complaint and lead_source:

curl --location '{base_url}/v2/leads' \
  --header 'Authorization: Bearer JWT_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
  "first_name": "Morgan",
  "last_name": "Delgado",
  "date_of_birth": "1984-02-19",
  "clinic_ids": [
    "5831"
  ],
  "email": "m.delgado@example.com",
  "mobile": "+1-555-0143",
  "complaint": "Pain in the left knee since surgery",
  "lead_source": "WEBSITE"
}'

Response

Success Response

Code: 202 Accepted

{
  "patient_id": "63120",
  "case_id": "SPRY_CASE_6aabbdec586d1e1ac56a37fd"
}

Response Fields

Field Type Description
patient_id string The patient the lead is for — the one sent, or the one just created.
case_id string or null The case that was opened, and the lead's reference — a case has exactly one lead, so GET /v2/leads?case_id= returns it once written. null only if a new patient's case couldn't be read back yet.

Error Response

Code: 400 Bad Request

{
  "code": "bad_request",
  "message": "Validation failed",
  "errors": [
    {
      "field": "patient_id",
      "message": "must be a string"
    }
  ]
}

Every error has the same shape — a machine-readable code, a message, and an errors[] array that's empty when there's nothing field-specific to report. The HTTP status is authoritative.

Status code Meaning
400 Bad Request bad_request The request body or parameters failed validation. errors[] names the offending fields.
401 Unauthorized unauthorized The access token is missing, malformed or expired.
403 Forbidden forbidden The request references a resource outside the token's organisation or clinic scope.
404 Not Found not_found No such resource, or it's outside your scope.
409 Conflict conflict The record conflicts with existing state — see the endpoint's notes.
429 Too Many Requests rate_limited Rate limit exceeded. Back off and retry.
500 Internal Server Error internal_error Unexpected server error.
502 Bad Gateway upstream_error Upstream service failed or timed out.

Notes

  • A lead is raised by opening a case. Send patient_id and a case is opened for that patient. Leave it out and send a new patient instead — the body of Create Patient — and the patient is created and their case opened in one step. complaint goes with either.
  • If a new patient's case can't be opened the request fails (502) and the patient is rolled back — though by then they may already show in patient search and have sent patient.created.
  • A new patient that matches an existing one is a 409, and nothing is created. The match is named in errors[0].message. Check their leads with GET /v2/leads?patient_id= before raising one with patient_id — a retry of a request that timed out lands here too, against the patient it created. The duplicate check matches within the first clinic on the name with the mobile, the date of birth or both, so it isn't done silently.
  • A new patient's lead belongs to the first of its clinic_ids. clinic_id, title and speciality need patient_id.
  • The new lead opens an intake request for the patient and, unless your organisation completes registration in person, sends them a link to the forms — as a lead raised in Spry does.
  • 202 as soon as the case exists, with patient_id and case_id — it doesn't wait for the lead. The lead is written a moment later, and a case has exactly one lead, so case_id is its reference: GET /v2/leads?case_id= (the Location header) returns it — an empty page means it hasn't landed yet, so read again shortly — and lead.created announces it. Location follows ?patient_id= instead, with case_id null, in the rare case a new patient's case couldn't be read back yet.
  • Not idempotent. A create that times out or fails with a 5xx may still have gone through, and retrying one with patient_id opens a second case and lead — check GET /v2/leads?patient_id= first. For a new patient, retrying is the check: if the first attempt went through, the retry is a 409 naming the patient it created.
  • With patient_id, a token limited to specific clinics must send clinic_id: without it the lead takes the patient's clinic, which that token may not be able to see.
  • To attach a referring physician, update the case once the lead exists — referring_physician_id isn't accepted here.
  • Emits the lead.created webhook, case.created for the case it opens and, for a new patient, patient.created.