Send verification code
Send a one-time code to a patient, opening the first half of the OTP gate.
POST /v1/patients/send-phone-verification-otpSends a verification code to the patient. It is the first of the two calls that open the OTP gate — the consent check that booking and patient-lookup routes require.
This route and POST /v1/otp/send do the
same thing over the same rail. They differ only at the verify step, where this
one also returns the patient's records. Pick a pair and stay on it.
Body
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumber | string | Yes* | The patient's number, without the dial code. |
countryCode | string | No | Dial code with the +. Defaults to +91. |
channel | string | No | whatsapp (default) or email. |
email | string | Yes* | *Required instead of phoneNumber when channel is email. |
Codes are delivered over WhatsApp on the default channel.
Try it
/v1/patients/send-phone-verification-otpBody
POST /v1/patients/send-phone-verification-otp{
"countryCode": "+91",
"phoneNumber": "9811100001"
}https://api.medos.oneUse a dedicated test key, and put your browser's address on its allowlist
A Developer API key authenticates on its own, so it only works from the addresses registered against it — and this page calls from your browser, not your servers. Unless your own public address is on the list you get a 403 naming it, which is the allowlist doing its job. Your browser may also reach us over IPv6 even when your server does not, so the address in the error is often not the one you expected. The key here is kept in memory only and never written to storage, but create a test key for it and deactivate that key when you are done.
Request
curl -sX POST "https://api.medos.one/v1/patients/send-phone-verification-otp" \
-H "x-api-key: $MEDOS_API_KEY" \
-H "x-end-user-id: $PATIENT_ID" \
-H "Content-Type: application/json" \
-d '{ "countryCode": "+91", "phoneNumber": "9876543210" }'Response
{
"success": true,
"message": "OTP sent successfully",
"channel": "whatsapp",
"phoneNumber": "+91****10"
}The destination comes back masked, and always under phoneNumber — even on the
email channel, where it holds a masked address such as a****@example.com. The
field name is kept for compatibility; read channel to know what it is.
Rate limits
Separate from the request rate limits, and stricter:
| Bucket | Limit |
|---|---|
| Per destination | 3 codes per 10 minutes |
Per end user (x-end-user-id), per workspace | 50 codes per 24 hours |
Both answer 429 with a real Retry-After — use the value rather than guessing
a backoff, since a patient-facing countdown built on an invented number is wrong
in a way the patient can see.
The daily budget is per end user
The 50-per-day ceiling is counted per x-end-user-id, which is why the header
is required here: without it, every patient your integration serves would share
one allowance. Send a stable identifier per patient — your own patient or CRM
id — and each patient also gets their own OTP gate, so the verification you rely
on when booking is the one you actually performed for that patient.
Common failures
| Status | Cause |
|---|---|
400 | No phoneNumber (or no email on the email channel). |
400 | channel is neither whatsapp nor email. |
400 | Delivery failed — a bad number, or the clinic's WhatsApp sender is not provisioned. |
429 | One of the two buckets above. Wait for Retry-After. |
Next
Verify and fetch records
Check the code, open the gate, and get the patient's records back.