Two flows, one mechanism. A pathology lab finishes a report and pushes it to us; a doctor writes a prescription in their own EMR and pushes that. In both cases the patient gets it on WhatsApp from the clinic they know, with no app to install and nothing to chase.
The clinic decides whether it is sent
This is the part to understand before you build. Uploading a document is not the same as sending it. Each clinic configures, per document type, whether we deliver automatically and whether quiet hours apply.
| Default | Lab reports | Prescriptions |
|---|---|---|
| Auto-send | On | Off — a person at the clinic releases each one |
| Quiet hours respected | Yes | Yes |
| Typical quiet window | 21:00 → 08:00 clinic time | 21:00 → 08:00 clinic time |
Prescriptions default to held because a prescription is a clinical instruction issued in a doctor's name. A clinic should switch that on deliberately rather than discover it has been happening.
Your response always tells you which way it went, so you never have to guess:
| `status` | Meaning |
|---|---|
SENT | Delivered to the patient's WhatsApp |
HELD | Stored, waiting — either auto-send is off, or it is inside quiet hours. releaseAfter says when |
FAILED | We tried and could not. statusReason says why |
SUPPRESSED | A sandbox key. Stored, never sent — this is how you test safely |
Lab report
{
"externalId": "LIS-RPT-55210",
"patientPhone": "+919811223344",
"patientName": "Rohan Mehta",
"label": "Complete Blood Count",
"file": {
"filename": "cbc-report.pdf",
"mimeType": "application/pdf",
"base64": "JVBERi0xLjQKJc..."
},
"meta": { "sampleCollectedAt": "2026-09-24T08:10:00+05:30", "labName": "Sunrise Diagnostics" }
}
201 Created
{
"resource": {
"type": "document",
"documentType": "lab_report",
"connectaiId": "68f1c2...",
"externalId": "LIS-RPT-55210",
"patientConnectaiId": "68a9...",
"status": "SENT",
"sentAt": "2026-09-24T09:15:02.118Z",
"action": "received"
}
}The patient receives the PDF as a real WhatsApp attachment they can keep, with label in the message text — send something a patient recognises ("Complete Blood Count"), not an internal code.
Prescription
{
"externalId": "EMR-RX-99871",
"patientExternalId": "HMS-PT-88213",
"doctorPhone": "+919812000111",
"label": "Prescription — 24 Sep",
"file": {
"filename": "prescription.pdf",
"url": "https://emr.example.com/rx/99871.pdf"
}
}
201 Created
{
"resource": {
"type": "document",
"documentType": "prescription",
"externalId": "EMR-RX-99871",
"status": "HELD",
"statusReason": "Auto-send is off for this document type",
"action": "received"
}
}HELD is the expected answer for a prescription on a clinic that has not opted in. It is stored and someone at the clinic releases it — treat it as success, not as an error.
Sending the file
| Field | Use it when |
|---|---|
file.base64 | You already hold the bytes. Simplest, one call |
file.url | The file is already on your storage. Must be https; we fetch it once |
file.filename | Always. This is what the patient sees |
file.mimeType | Optional. Inferred from the filename when omitted |
- PDF, JPEG and PNG only.
- 15 MB maximum.
- We store our own copy, so your URL does not have to stay reachable afterwards.
Identifying the patient
Send patientExternalId if you have used one with us before. Otherwise patientPhone plus patientName is enough — an unknown patient is created and linked to the clinic. A lab holds a name and a phone, not our patient ids, and a real report should never go undelivered over a bookkeeping detail.
Checking delivery
{
"resource": {
"type": "document",
"documentType": "lab_report",
"externalId": "LIS-RPT-55210",
"status": "HELD",
"statusReason": "Inside the clinic's quiet hours",
"releaseAfter": "2026-09-25T02:30:00.000Z",
"action": "found"
}
}You can also subscribe to document.delivered and document.failed webhooks instead of polling. See Webhooks.
externalId returns the first result and does NOT deliver again. That is deliberate: a retry after a network timeout must never put the same report in front of a patient twice.