Server-side APIEndpointsPatients

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-otp

Sends 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

FieldTypeRequiredDescription
phoneNumberstringYes*The patient's number, without the dial code.
countryCodestringNoDial code with the +. Defaults to +91.
channelstringNowhatsapp (default) or email.
emailstringYes**Required instead of phoneNumber when channel is email.

Codes are delivered over WhatsApp on the default channel.

Try it

POST/v1/patients/send-phone-verification-otp

Body

Request
POST /v1/patients/send-phone-verification-otp
{
  "countryCode": "+91",
  "phoneNumber": "9811100001"
}
to https://api.medos.one
Use 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:

BucketLimit
Per destination3 codes per 10 minutes
Per end user (x-end-user-id), per workspace50 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

StatusCause
400No phoneNumber (or no email on the email channel).
400channel is neither whatsapp nor email.
400Delivery failed — a bad number, or the clinic's WhatsApp sender is not provisioned.
429One 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.

On this page