Patient Share (Scan-and-Share) & HPR (Healthcare Professional Registry)
1. Patient Share / Scan-and-Share
What this is for: This is the "walk up to the reception desk and scan a QR code" flow. A patient opens their ABHA (PHR) app, scans the facility's counter QR code, and their basic profile (name, ABHA address, DOB, etc.) is sent straight to the facility's reception system — no manual data entry, no paperwork. In return, the facility hands the patient a queue token number, and the patient can watch their token number update live in their PHR app while they wait.
Under the hood this is a round trip through the ABDM gateway: the patient's PHR app tells ABDM "I'm sharing my profile with this HIP", ABDM calls our system (acting as the HIP — Health Information Provider — for the facility), we generate a token number and hand it back to ABDM, and ABDM relays that acknowledgement back to the patient's app (where we also act as the HIU — Health Information User — receiving that acknowledgement). The same request/response pattern is reused for a secondary "what's the current queue number at this counter" query used to show live queue progress.
Code lives in src/modules/abdm/hip/patient-share/:
patient-share.routes.ts— outbound/internal endpoints (dashboard + running-token query), mounted at/v1/patient-share(seesrc/app.ts)patient-share.callback.routes.ts— inbound ABDM callback endpoints (no separate mount prefix beyond the raw/api/v3/...and/api/hiecm/...paths ABDM POSTs to directly)patient-share.controller.ts/patient-share.callback.controller.ts— request handlerspatient-share.service.ts/patient-share.callback.service.ts— business logicpatient-share.schema.ts— zod validation schemaspatient-share.client.ts— outbound HTTP calls to ABDM
Outbound / internal endpoints (/v1/patient-share)
GET /v1/patient-share/stats/:facilityId
Plain English: returns the "how are we doing today" numbers for the reception-desk dashboard — how many patients have scanned in, how many were successfully checked in, how many failed.
- Who calls this: Our own frontend (orvo-hub reception dashboard), authenticated via the
X-HIP-IDheader (requireXHipIdmiddleware — this is a facility identifier header, not a bearer token; there is no additional JWT check on this route). - Request: No body. Path param
facilityId— this is the internal Orvo facility ID, not the ABDM HIP ID. - Example response:
{
"success": true,
"message": "Success",
"data": {
"allTime": { "total": 482, "acknowledged": 460, "failed": 14, "received": 8 },
"today": { "total": 37, "acknowledged": 35, "failed": 2 }
},
"requestId": null
}
- What triggers it / what happens next: Called on dashboard page load and periodic refresh. No side effects — it's a read-only aggregate query.
GET /v1/patient-share/events
Plain English: the paginated list of "who scanned in today" — every patient-share event recorded for the facility, newest first.
- Who calls this: Our reception dashboard frontend, via
X-HIP-IDheader. - Request: Query string, validated against
PatientShareEventsQuerySchema:
{
"limit": 20,
"offset": 0,
"status": "ACKNOWLEDGED"
}
status is optional (RECEIVED | ACKNOWLEDGED | FAILED | EXPIRED); omit to return all.
- Example response:
{
"success": true,
"message": "Success",
"data": {
"events": [
{
"id": "clv9x...",
"facilityId": "fac_9182",
"requestId": "9b9f6b1e-2b40-4f2e-8a4c-3f2b6e2a11aa",
"abhaAddress": "rajesh.kumar@abdm",
"abhaNumber": "91-1234-5678-9012",
"hipCode": "IN0310000702_1",
"counterId": "counter-1",
"tokenNumber": "482913",
"linkingToken": null,
"latitude": 28.6139,
"longitude": 77.209,
"status": "ACKNOWLEDGED",
"createdAt": "2026-07-18T04:12:03.000Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
}
Note rawPayload (the full ABDM payload) is deliberately omitted here for size — fetch the single-event endpoint for that.
- What triggers it / next: Reception desk list/refresh view. Read-only.
GET /v1/patient-share/events/:requestId
Plain English: the full detail of one specific scan-in event, including the raw payload ABDM sent us — mainly used to debug an event that looks wrong or didn't behave as expected.
- Who calls this: Our reception dashboard frontend, via
X-HIP-IDheader (the header is required by the route but the lookup itself is byrequestIdonly, not additionally scoped to the facility). - Request: No body. Path param
requestId— this is the ABDM-assigned correlation ID, not our internal database ID. - Example response: Same shape as above, but includes
rawPayload(the exact JSON ABDM originally POSTed to the HIP callback). - 404 if no event exists for that
requestId. - What triggers it / next: Support/debugging click-through from the events list.
POST /v1/patient-share/events/:requestId/retry
Plain English: if our earlier acknowledgement to ABDM failed (e.g. a network blip), this button re-sends it, so the patient's queue token still gets confirmed without asking them to scan again.
- Who calls this: Our reception dashboard frontend, via
X-HIP-IDheader. - Request: No body. Path param
requestId. - Example response (success):
{ "success": true, "message": "Acknowledged successfully", "data": null }
- Example response (already acknowledged, no-op):
{ "success": true, "message": "Already acknowledged", "data": null }
- 404 if the event doesn't exist. 409 if the event is
EXPIRED— ABDM only holds the patient's app open for a short acknowledgement window (observed as ABDM error codes ABDM-1015/ABDM-1030, "Request Timed out"); once that window closes therequestIdis dead on ABDM's side and no retry can ever succeed, so the event is markedEXPIREDand the UI is told to stop offering retries. - What triggers it / next: Manual retry action by reception staff after seeing a
FAILEDevent.
POST /v1/patient-share/running-token/status
Plain English: asks ABDM "what queue number is currently being served at counter X of this facility?" — used so a waiting patient's PHR app can show live queue progress. The actual answer doesn't come back in this HTTP response; it arrives later via a separate callback (below).
- Who calls this: A PHR client app, authenticated with both a bearer token (forwarded to ABDM as
X-AUTH-TOKEN) and theX-HIP-IDheader. - Request (
RunningTokenStatusRequestSchema):
{
"hipId": "IN0310000702_1",
"context": "counter-1"
}
- Example response:
{
"success": true,
"message": "Accepted",
"data": { "requestId": "5c8a1f2e-77b3-4e5a-9b1c-2d3e4f5a6b7c" },
"requestId": null
}
Returned with HTTP 202 — ABDM only acknowledges receipt of the query here.
- What triggers it / next: A PHR app polling/subscribing to queue status. The actual running token number is delivered asynchronously to the requesting HIU via the
RUNNING_TOKEN_ON_STATUScallback described below.
PATCH /v1/orvo/patient-share/{tokenNumber} — ORVO appointment correlation callback
Plain English: one-way inbound callback from api.orvo.app into abha.orvo.app when the appointment attached to a Scan & Share token changes status. This endpoint links the Orvo appointment data back onto every matching PatientShareEvent row for that facility, so the reception dashboard and the underlying event record stay in sync.
- Who calls this:
api.orvo.app. - Path param:
tokenNumber— the queue token assigned during the scan-and-share flow. - Request body (
OrvoPatientShareUpdateSchema):
{
"appointmentId": "appt-1",
"clinicId": "550e8400-e29b-41d4-a716-446655440000",
"patientId": "550e8400-e29b-41d4-a716-446655440001",
"doctorId": "550e8400-e29b-41d4-a716-446655440002",
"doctorName": "Dr. John Doe",
"appointmentStatus": "Booked",
"appointmentDate": "2026-08-21",
"meta": {
"departmentId": "550e8400-e29b-41d4-a716-446655440003",
"departmentName": "Cardiology"
}
}
appointmentDate accepts Orvo's date-only YYYY-MM-DD value as well as a full ISO 8601
timestamp. meta and its department fields may be omitted or null when the appointment is
not assigned to a department. appointmentStatus accepts the canonical values used by this API,
and legacy No-Show / No Show spellings are normalized to noShown.
- What gets stored:
appointmentId,doctorId,doctorName,appointmentDate,meta,departmentId,departmentName,orvoPatientId, andappointmentStatusare persisted onPatientShareEvent, along with the raw request body inorvoCallbackPayload. - What happens next: The same data is broadcast inside
abha.orvo.appto the facility dashboard viapatient-share:appointment-updated; if the status becomesOngoingorResumed, the service also re-queries ABDM's running-token status for each affected counter. - Success response:
{ "success": true, "message": "Patient share updated successfully", "data": { "updated": 2 } }
Inbound ABDM callbacks
These four routes are POSTed to directly by the ABDM gateway. There is no bearer-token or session auth on any of them — ABDM does not reliably send X-HIP-ID either, so the non-blocking attachXHipId-style handling is used elsewhere in the codebase for header-optional cases; these specific callback routes rely purely on the body's own hipId/abhaAddress fields to resolve context. Every inbound callback here is logged via createInboundCallbackLogger('patient-share') for auditability, and every controller method replies immediately with 202 before doing any processing — ABDM only allows the HIP a short window to acknowledge, so processing happens asynchronously after the response is already sent.
POST /api/v3/hip/patient/share — Inbound ABDM callback
Plain English: this is the actual "the patient just scanned the QR" event. ABDM sends us the patient's profile the instant they scan, and we must hand back a queue token number within seconds.
- Who calls this: ABDM gateway only (server-to-server), no auth headers expected from our side.
- Request (
PatientShareCallbackSchema, extendingProfileShareRequestSchema):
{
"intent": "PROFILE_SHARE",
"metaData": {
"hipId": "IN0310000702_1",
"context": "counter-1",
"hprId": null,
"hfrId": null,
"latitude": "28.6139",
"longitude": "77.2090"
},
"profile": {
"patient": {
"abhaAddress": "rajesh.kumar@abdm",
"abhaNumber": "91-1234-5678-9012",
"name": "Rajesh Kumar",
"gender": "M",
"yearOfBirth": "1991",
"monthOfBirth": "04",
"dayOfBirth": "24",
"phoneNumber": "9876543210",
"address": {
"line": "12 MG Road",
"district": "Bengaluru Urban",
"state": "Karnataka",
"pincode": "560001"
}
}
},
"token": "a1b2c3d4-link-token-example"
}
Note: ABDM sends latitude/longitude as strings here (a known ABDM quirk) even though the DB column and the PHR-app's own outbound request use numbers; the schema coerces this automatically. token is a V3 addition — a link token issued at QR-scan time (valid 24h) that lets the facility later perform HIP-initiated care-context linking for this patient without a separate demographic-auth step.
- Response: Immediate
202 { "status": "accepted" }. There is no synchronous "processed" response body. - What happens next: In the background, the service (1) resolves the internal facility from
hipId, (2) generates a 6-digit token number, (3) calls ABDM'son-shareAPI to acknowledge — this must happen before any database writes, since a late acknowledgement is rejected by ABDM with error ABDM-1015 "Request Timed out" and therequestIdbecomes permanently dead — (4) persists aPatientShareEventrow with statusACKNOWLEDGED/FAILED/EXPIRED, (5) persists the V3tokenas aLinkTokenRequestif present, and (6) on success, creates a facility notification and emits a Pusher event (patient-share:acknowledged) so the reception desk UI updates live without a page refresh, showing the patient's name and token number.
POST /api/v3/hiu/patient/on-share — Inbound ABDM callback
Plain English: this is ABDM relaying back, to us acting as the patient's HIU, the token number that the HIP (possibly a different facility's system, or our own HIP side) just assigned — so the patient's own PHR session can be told "here's your number."
- Who calls this: ABDM gateway only, no auth headers.
- Request (
HiuOnShareCallbackSchema):
{
"acknowledgement": {
"status": "SUCCESS",
"abhaAddress": "rajesh.kumar@abdm",
"profile": {
"context": "counter-1",
"tokenNumber": "482913",
"expiry": 180
}
},
"error": null,
"response": { "requestId": "9b9f6b1e-2b40-4f2e-8a4c-3f2b6e2a11aa" }
}
expiry is sent by ABDM as a numeric string in practice (e.g. "180"); the schema coerces it so the callback isn't rejected and silently dropped.
- Response: Immediate
202 { "status": "accepted" }. - What happens next: Emits a per-ABHA-address Pusher event
patient-share:on-shareso the patient's own PHR tab can display the assigned token number live, and — if the originalPatientShareEventfor thatrequestIdcan be found and the status isSUCCESS— writes a patient-facing DB notification ("Queue token assigned").
POST /api/v3/hip/patient/running-token/status — Inbound ABDM callback
Plain English: ABDM, on behalf of a waiting PHR user, is asking our HIP side "what number are you currently serving at this counter?"
- Who calls this: ABDM gateway only, no auth headers.
- Request (
RunningTokenStatusCallbackSchema):
{
"hipId": "IN0310000702_1",
"context": "counter-1"
}
- Response: Immediate
202 { "status": "accepted" }. - What happens next: Resolves the facility from
hipId, looks up the most recently issued token number for that counter (context) fromPatientShareEvent, calls ABDM'son-running-token-statusAPI to answer back with that number (best-effort — failures are logged, not retried), and emits a Pusher eventpatient-share:running-token-queriedto the facility dashboard.
POST /api/v3/hiu/running-token/on-status — Inbound ABDM callback
Plain English: ABDM delivering the answer to the running-token query above back to whichever app originally asked (our HIU side), so a live queue-progress display can be updated.
- Who calls this: ABDM gateway only, no auth headers.
- Request (
RunningTokenOnStatusCallbackSchema):
{
"token": {
"hipId": "IN0310000702_1",
"context": "counter-1",
"runningTokenNumber": "482913",
"averageTokenServiceTimeInMinutes": 5
},
"error": null,
"response": { "requestId": "5c8a1f2e-77b3-4e5a-9b1c-2d3e4f5a6b7c" }
}
- Response: Immediate
202 { "status": "accepted" }. - What happens next: Resolves the facility from
token.hipIdand emits a Pusher eventpatient-share:running-token-updatedwith the current number and (if supplied) the average per-patient service time, for dashboard/queue-display consumers.
2. HPR — Healthcare Professional Registry
What this is for: HPR is India's national registry of doctors and other healthcare professionals — completely separate from ABHA (which is the patient health ID). This module lets a doctor or nurse create an HPR ID (their professional identity number), verify their identity via Aadhaar or mobile OTP, fill in their full professional profile (qualifications, registration numbers, work details), upload supporting documents, and — separately — register/link the facility (hospital/clinic) they work at into the Health Facility Registry (HFR). Once done, the professional can log in and manage their HPR account going forward.
Everything under this module proxies through to ABDM's NHPR (National Health Professional Registry) sandbox APIs. This backend does two things NHPR requires beyond plain proxying:
- Server-side RSA encryption of sensitive fields (Aadhaar numbers, OTPs, passwords, mobile numbers) via
hprCryptoService, using ABDM's public certificate (fetchable directly atGET /auth/cert, though clients normally never need to call it themselves). - Session-token forwarding — after login, the caller's HPR session token (
hprToken) is captured from theAuthorizationheader (viahprRequestContext, anAsyncLocalStorage-based per-request context wired in as router-level middleware) and auto-injected into outbound request bodies ashprToken/hpr_tokenwherever ABDM expects it there in addition to (or instead of) an auth header.
Code lives in src/modules/abdm/hpr/, mounted at /v1/hpr (see src/app.ts):
hpr.routes.ts— all ~65 endpointshpr.controller.ts/hpr.service.ts/hpr.client.ts— thin pass-through layers (controller → service → client → ABDM), each stage adding RSA-encryption or field-injection where neededhpr.schema.ts— zod validationhpr.docs.ts— OpenAPI documentation source (used as the basis for the examples below)hpr-request-context.ts— per-requestAsyncLocalStoragecarrying the forwardedAuthorizationheaderhpr-aadhaar-session.store.ts— in-memory/session state for the Aadhaar flow
Because this module is a broad proxy, nearly every response is a passthrough of whatever ABDM returns, wrapped in this backend's standard envelope: { "success": true, "message": "Success", "data": <ABDM's response>, "requestId": null }. Examples below show a realistic data payload per ABDM's documented contract; treat exact field sets as illustrative since ABDM's sandbox responses for deeply-nested objects are not fully pinned down in the schema (marked loose()/passthrough where that applies).
Registration: Aadhaar identity verification
Two parallel paths exist to Aadhaar-verify a new registrant, both converging on a txnId-carrying demographic payload that feeds HPR ID creation: (a) the gateway/DigiLocker hosted-portal flow, and (b) the direct-OTP flow this backend drives itself with server-side RSA encryption.
POST /v1/hpr/registration/aadhaar/generate-link
Plain English: starts the "verify via ABDM's own hosted Aadhaar consent page" path — we never see the doctor's raw Aadhaar number for this option.
- Who calls this: Our frontend (orvo-hub/orvo-web HPR registration wizard), no auth required yet (pre-login).
- Request (
GenerateAadhaarLinkSchema):
{ "scopes": ["nhpr-register"], "source": "NHPR" }
- Example response
data:
{ "txnId": "txn-abc-123", "link": "https://healthid.ndhm.gov.in/verify?token=..." }
- Next: User completes Aadhaar consent+OTP inside that hosted page, then the frontend calls
verifyorverify-gatewaywith the returnedtxnId.
POST /v1/hpr/registration/aadhaar/generate-otp
Plain English: the alternative to the hosted-portal flow — enter your Aadhaar number directly in our form, and ABDM texts an OTP to your Aadhaar-linked mobile.
- Who calls this: Our frontend, pre-login.
- Request (
AadhaarOtpGenerateRequestSchema):{ "aadhaar": "234512345678" }— the plaintext Aadhaar number is RSA/PKCS1v15-encrypted server-side before being sent to ABDM. - Example response
data:{ "txnId": "txn-abc-124" } - Next: Call
verify-otpwith the OTP received on the Aadhaar-linked mobile.
POST /v1/hpr/registration/aadhaar/verify-otp
Plain English: submits the OTP the doctor received, completing direct-OTP Aadhaar verification, and gets back their Aadhaar KYC details to pre-fill the registration form.
- Who calls this: Our frontend, pre-login.
- Request (
AadhaarVerifyOtpRequestSchema):
{ "otp": "123456", "txnId": "txn-abc-124", "domainName": "@hpr.abdm", "idType": "hpr_id" }
domainName/idType default when omitted, but leaving them out has been observed to surface a misleading "Failed to retrieve aadhaar transaction details" error even though txnId is valid — always send them.
- Example response
data:
{
"txnId": "txn-abc-124",
"name": "Rajesh Kumar",
"gender": "M",
"yearOfBirth": "1991",
"monthOfBirth": "04",
"dayOfBirth": "24",
"photo": "base64....",
"address": { "state": "Karnataka", "district": "Bengaluru Urban", "pincode": "560001" }
}
- Next: Feeds
hpid-suggestionsand, ultimately,create-hprid.
POST /v1/hpr/registration/aadhaar/verify
Plain English: the gateway/DigiLocker-flow counterpart to verify-otp — fetches the verified Aadhaar demographics (and any existing linked HPR record) once the user has completed consent in ABDM's hosted portal.
- Who calls this: Our frontend, pre-login.
- Request (
TxnIdSchema):{ "txnId": "txn-abc-123" } - Example response
data: same demographic shape asverify-otpabove, plus an existing professional record if one is already linked to this Aadhaar. - Next: Same as
verify-otp— feeds HPR ID creation.
POST /v1/hpr/registration/aadhaar/verify-gateway
Plain English: an alternate entry point for the same gateway/DigiLocker flow, for callers that only have a txnId from the consent redirect (no OTP to submit — that step already happened in ABDM's portal). Internally calls the same upstream ABDM endpoint as verify-otp.
- Who calls this: Our frontend, pre-login.
- Request (
TxnIdSchema):{ "txnId": "txn-abc-123" } - Example response
data: Same shape asverify/verify-otp. - Next: Same as above.
POST /v1/hpr/registration/aadhaar/check-account
Plain English: "does this person already have an HPR account?" — used to redirect an already-registered doctor to login instead of letting them re-register.
- Who calls this: Our frontend, pre-login.
- Request (
TxnIdSchema):{ "txnId": "txn-abc-123" } - Example response
data:trueorfalse. - Next: If
true, the wizard branches to login; iffalse, continues tohpid-suggestions/create-hprid.
Demographic auth & mobile OTP registration
POST /v1/hpr/registration/aadhaar/demographic-auth-mobile
Plain English: an alternative to OTP — confirms a mobile number matches UIDAI's records for this Aadhaar transaction without sending an OTP at all (useful when OTP delivery is unavailable).
- Who calls this: Our frontend, pre-login.
- Request (
MobileVerifyRequestSchema):{ "txnId": "txn-abc-123", "mobileNumber": "9876543210" }— mobile is RSA-encrypted server-side. - Example response
data:{ "verified": true } - Next: Proceeds through registration as an identity-confirmation step.
POST /v1/hpr/registration/aadhaar/generate-mobile-otp
Plain English: sends an OTP to a contact mobile number supplied during registration (used when it differs from the Aadhaar-linked mobile).
- Request (
GenerateMobileOtpRequestSchema):{ "mobile": "9876543210", "txnId": "txn-abc-123" }— note the mobile number itself is sent as-is here (not encrypted); only the OTP on the verify call is encrypted. - Example response
data:{ "txnId": "txn-abc-123" } - Next: Call
verify-mobile-otp.
POST /v1/hpr/registration/aadhaar/verify-mobile-otp
- Request (
VerifyMobileOtpRequestSchema):{ "otp": "123456", "txnId": "txn-abc-123" }— OTP RSA-encrypted server-side. - Example response
data:{ "verified": true } - Next: Proceeds to HPR ID username suggestions/creation.
HPR ID creation
POST /v1/hpr/registration/aadhaar/hpid-suggestions
Plain English: suggests available username-style HPR IDs based on the doctor's name (e.g. rajesh.kumar, rajesh.kumar1).
- Request (
TxnIdSchema):{ "txnId": "txn-abc-123" } - Example response
data:["rajesh.kumar", "rajesh.kumar1", "rajeshk.doc"] - Next: The chosen (or custom) name is passed as
hprIdtocreate-hprid.
POST /v1/hpr/registration/aadhaar/create-hprid
Plain English: the final registration step — creates the actual HPR ID account with a username and password, so the doctor can subsequently log in.
- Request (
CreateHprIdRequestSchema):
{
"txnId": "txn-abc-123",
"email": "rajesh.kumar@example.com",
"domainName": "@hpr.abdm",
"firstName": "Rajesh",
"lastName": "Kumar",
"password": "SecurePass123!",
"hprId": "rajesh.kumar",
"sourceType": "AADHAAR",
"hpCategoryCode": 1,
"hpSubCategoryCode": 1,
"role": 1
}
email and password are sent as plaintext by the caller and RSA-encrypted server-side before forwarding. hpCategoryCode/role: 1 = Doctor.
- Example response
data:
{ "hprId": "rajesh.kumar@hpr.abdm", "hprIdNumber": "91-1234-5678-9012" }
- Next: Account can now log in via
/auth/password,/auth/mobile-otp/*, or/auth/aadhaar-otp/*, then complete the full professional profile via/doctors/register, and later link/register a facility via the facility onboarding endpoints.
Login (authentication)
None of these carry a forwarded HPR token — they're what establishes one (the outbound client falls back to ABDM's gateway token for these calls).
GET /v1/hpr/auth/cert
Plain English: fetches ABDM's public encryption certificate. Exposed mainly for parity/debugging — this backend already does all RSA encryption server-side, so most clients never need to call this.
- Who calls this: Public, no auth.
- Response: raw PEM certificate string.
POST /v1/hpr/auth/password
Plain English: standard username+password login for an existing HPR account.
- Request (
AuthPasswordRequestSchema):{ "idType": "hpr_id", "domainName": "@hpr.abdm", "hprId": "rajesh.kumar", "password": "SecurePass123!" }— password RSA-encrypted server-side. - Example response
data:{ "hprToken": "eyJhbGciOi..." } - Next:
hprTokenmust be sent (raw, noBearerprefix once forwarded upstream — this backend accepts it Bearer-prefixed from the frontend and strips the prefix) asAuthorizationon all subsequent authenticated calls.
POST /v1/hpr/auth/mobile-otp/send
- Request (
HprAuthMobileOtpRequestSchema):{ "mobile": "9876543210" } - Example response
data:{ "txnId": "txn-login-555" }(normalized totxnIdregardless of whether ABDM usedtxnIdortransactionId) - Next:
/auth/mobile-otp/verify.
POST /v1/hpr/auth/aadhaar-otp/init
- Request (
AadhaarOtpInitRequestSchema):{ "idType": "hpr_id", "domainName": "@hpr.abdm", "authMethod": "AADHAAR_OTP", "hprId": "rajesh.kumar" } - Example response
data:{ "txnId": "txn-login-556" } - Next:
/auth/aadhaar-otp/confirm.
POST /v1/hpr/auth/aadhaar-otp/confirm
Plain English: completes Aadhaar-OTP login and issues the session token.
- Request (
AadhaarOtpVerifyRequestSchema):{ "otp": "123456", "txnId": "txn-login-556" }— unlike most OTP fields elsewhere, this OTP is forwarded as plaintext to match ABDM's contract;otpcan also be omitted (defaults to empty string) for the gateway-driven login variant. - Example response
data:{ "hprToken": "eyJhbGciOi..." }
POST /v1/hpr/auth/mobile-otp/verify
- Request (
HprAuthMobileOtpVerifyRequestSchema):{ "otp": "123456", "txnId": "txn-login-555", "mobile": "9876543210" }— OTP RSA-encrypted when present. Internally retries against an older ABDM endpoint on 404/405 orHIS-500for backward compatibility. - Example response
data(single account):{ "hprToken": "eyJhbGciOi..." } - Example response
data(mobile linked to multiple HPR accounts):
{
"txnId": "txn-login-555",
"hprIds": [
{ "hpId": "91-1234-5678-9012", "hprId": "rajesh.kumar" },
{ "hpId": "91-2222-3333-4444", "hprId": "rajesh.k.nurse" }
]
}
- Next: If multiple accounts are returned, call
/auth/authorized-tokento pick one.
POST /v1/hpr/auth/authorized-token
Plain English: "I have multiple HPR accounts on this mobile — give me the token for this specific one."
- Request (
HprAuthorizedTokenRequestSchema):{ "hpId": "91-1234-5678-9012", "txnId": "txn-login-555" } - Example response
data:{ "hprToken": "eyJhbGciOi..." }
Account management
GET /v1/hpr/account/information
Plain English: the doctor's full HPR profile, for display in an account/profile screen.
- Who calls this: Our frontend, with the HPR session token via
Authorization(bearer). - Example response
data: the full professional record (personal info, qualifications, contact info — same overall shape as theregister/updaterequest body, echoed back).
GET /v1/hpr/account/id-card
Plain English: fetches the doctor's official HPR ID card (image/PDF) for viewing or download.
- Who calls this: Our frontend, with
Authorizationbearer. - Example response
data:{ "idCard": "base64-encoded-image-or-pdf..." }
GET /v1/hpr/auth/logout
Plain English: ends the HPR session on ABDM's side.
- Who calls this: Our frontend, with
Authorizationbearer. Frontend should discard its locally stored token regardless of this call's outcome. - Example response
data:{ "message": "Logged out successfully" }
Professional registration & update
POST /v1/hpr/doctors/register
Plain English: fills in the doctor's actual professional profile — personal details, qualifications, current workplace — after the bare HPR ID account already exists. This is the step that makes the registry entry meaningful, not just a login.
- Who calls this: Our frontend, with
Authorizationbearer (the session'shprTokenis auto-injected into the body ashprToken, so callers don't need to include it). - Request (
RegisterProfessionalRequestSchema):
{
"practitioner": {
"healthProfessionalType": "doctor",
"officialMobile": "9876543210",
"officialEmail": "rajesh.kumar@example.com",
"personalInformation": {
"firstName": "Rajesh",
"lastName": "Kumar",
"gender": "M",
"dateOfBirth": "1991-04-24",
"nationality": "356",
"languagesSpoken": "1,2",
"category": "C"
},
"registrationAcademic": {
"category": "MBBS",
"registrationData": [
{
"qualifications": [
{ "qualificationName": "MBBS", "collegeId": "1022", "yearOfPassing": "2015" }
]
}
]
},
"currentWorkDetails": { "facilityId": "IN2810000123" }
}
}
Deeper nested fields (registrationAcademic, currentWorkDetails, communicationAddress, contactInformation) are intentionally left loosely typed since they vary widely by profession/category — validate the exact shape against a live NHPR sandbox response before hardening.
- Example response
data:{ "hprId": "91-1234-5678-9012", "status": "REGISTERED" } - Next: Profile is now complete; documents can be uploaded and the professional can be found via
/doctors/fetch-professional-infoor the search endpoints.
POST /v1/hpr/doctors/fetch-professional-info
Plain English: looks up a registered professional's profile by HPR ID.
- Request (
FetchProfessionalRequestSchema):{ "practitioner": { "id": "91-1234-5678-9012" } }(optionally narrowed withname,contactNumber,state,registrationNumber,stateCouncilName) - Example response
data: the professional's registered profile, same shape as theregisterpayload.
POST /v1/hpr/doctors/update-professional
Plain English: edits an existing professional profile — same payload shape as registration, applied to an account that's already registered.
- Who calls this: Our frontend, with
Authorizationbearer (hprTokenauto-injected). - Request: Same shape as
RegisterProfessionalRequestSchema. - Example response
data:{ "hprId": "91-1234-5678-9012", "status": "UPDATED" }
Document management
POST /v1/hpr/doctors/fetch-documents
Plain English: lists which documents (degree certificate, ID photo, etc.) are already on file for a professional, and which slots are still empty.
- Request (
FetchDocumentsRequestSchema):{ "hprid": "91-1234-5678-9012" } - Example response
data:
[
{ "document_id": 1, "document_type": "profilePhoto", "uploaded": true },
{ "document_id": 2, "document_type": "degreeCertificate", "uploaded": false }
]
- Next:
document_idvalues feedupload-document.
POST /v1/hpr/uploads/upload-document
Plain English: uploads the actual scanned certificate/photo files.
- Who calls this: Our frontend, with
Authorizationbearer (hpr_tokenauto-injected). - Request (
UploadDocumentRequestSchema):
{
"document": [
{
"document_id": 2,
"document_type": "degreeCertificate",
"fileType": "application/pdf",
"data": "JVBERi0xLjQK..."
}
]
}
- Example response
data:{ "status": "UPLOADED", "document_id": 2 }
Email verification
POST /v1/hpr/doctors/generate-email-otp
- Request (
GenerateEmailOtpRequestSchema):{ "emailAddress": "rajesh.kumar@example.com", "otp_type": "official" } - Example response
data:{ "message": "OTP sent" }
POST /v1/hpr/doctors/resend-email-otp
- Request (
ResendEmailOtpRequestSchema):{ "emailAddress": "rajesh.kumar@example.com", "otp_type": "official" } - Example response
data:{ "message": "OTP resent" }
POST /v1/hpr/doctors/verify-email-otp
Plain English: confirms the emailed OTP and sets that address as the doctor's official verified email on the profile.
- Who calls this: Our frontend, with
Authorizationbearer (hpr_tokenauto-injected). - Request (
VerifyEmailOtpRequestSchema):{ "hpr_id": "91-1234-5678-9012", "officialEmail": "rajesh.kumar@example.com", "emailOtp": "123456" } - Example response
data:{ "verified": true }
Mobile verification (profile update)
Distinct from the registration-time mobile OTP endpoints above — these operate on an already-registered professional's official mobile number and always need an active session.
POST /v1/hpr/doctors/generate-mobile-otp
- Who calls this: Our frontend,
Authorizationbearer (hpr_tokenauto-injected). - Request (
GenerateProfileMobileOtpRequestSchema):{ "officialMobile": "9876543210" }— RSA-encrypted server-side. - Example response
data:{ "txnId": "txn-mob-777" }
POST /v1/hpr/doctors/regenerate-mobile-otp
- Request (
RegenerateProfileMobileOtpRequestSchema):{ "officialMobile": "9876543210" } - Example response
data:{ "txnId": "txn-mob-777" }
POST /v1/hpr/doctors/verify-mobile-otp
- Request (
VerifyProfileMobileOtpRequestSchema):{ "txnId": "txn-mob-777", "otp": "123456" }— OTP RSA-encrypted server-side. - Example response
data:{ "verified": true, "officialMobile": "9876543210" }
Password management
POST /v1/hpr/password/recover-mobile-send
Plain English: forgot-password flow, step 1 — send a recovery OTP to the account's registered mobile.
- Request (
RecoverPasswordMobileSendSchema):{ "hprId": "rajesh.kumar" }— no active session needed. - Example response
data:{ "txnId": "txn-pwd-888" }
POST /v1/hpr/password/recover-mobile-verify
- Request (
RecoverPasswordMobileVerifySchema):{ "txnId": "txn-pwd-888", "otp": "123456" }— OTP RSA-encrypted server-side. - Example response
data:{ "txnId": "txn-pwd-888", "verified": true }
POST /v1/hpr/password/reset
- Request (
ResetPasswordSchema):{ "txnId": "txn-pwd-888", "newPassword": "NewSecurePass1!" }— RSA-encrypted server-side. - Example response
data:{ "message": "Password reset successful" }
POST /v1/hpr/password/recover-aadhaar
Plain English: alternate recovery path when the registered mobile is unreachable — recover via Aadhaar OTP instead.
- Request (
RecoverPasswordAadhaarSchema):{ "hprId": "rajesh.kumar" } - Example response
data:{ "txnId": "txn-pwd-889" }
POST /v1/hpr/password/recover-aadhaar-confirm
- Request (
RecoverPasswordConfirmAadhaarSchema):{ "txnId": "txn-pwd-889", "otp": "123456" }— RSA-encrypted server-side. - Example response
data:{ "txnId": "txn-pwd-889", "verified": true }→ then/password/reset.
POST /v1/hpr/password/change
Plain English: for a logged-in user who already knows their password and just wants to change it (no OTP needed).
- Request (
ChangePasswordSchema):{ "oldPassword": "SecurePass123!", "newPassword": "NewSecurePass1!" }— both RSA-encrypted server-side. (Note: this backend's implementation does not itself enforce the sessionAuthorizationheader on this route — verify ABDM's actual requirement before relying on that in production.) - Example response
data:{ "message": "Password changed" }
Forgot HPID (recovering a forgotten username)
POST /v1/hpr/forgot-hpid/aadhaar-generate-otp
- Request (
ForgotHpidAadhaarGenerateOtpSchema):{ "aadhaar": "234512345678", "iagree": true }— Aadhaar RSA-encrypted;iagreeis a required consent flag. - Example response
data:{ "txnId": "txn-hpid-990" }
POST /v1/hpr/forgot-hpid/aadhaar-verify
- Request (
ForgotHpidAadhaarVerifySchema):{ "txnId": "txn-hpid-990", "otp": "123456" }— RSA-encrypted. - Example response
data:{ "hprIds": ["rajesh.kumar"] }
POST /v1/hpr/forgot-hpid/mobile-generate-otp
- Request (
ForgotHpidMobileGenerateOtpSchema):{ "mobileNumber": "9876543210" }— RSA-encrypted. - Example response
data:{ "txnId": "txn-hpid-991" }
POST /v1/hpr/forgot-hpid/mobile-verify
Plain English: since one mobile can map to more than one account, recovering by mobile also needs matching demographic details to pick the right one.
- Request (
ForgotHpidMobileVerifySchema):
{
"txnId": "txn-hpid-991",
"otp": "123456",
"firstName": "Rajesh",
"lastName": "Kumar",
"yearOfBirth": "1991",
"monthOfBirth": "04",
"dayOfBirth": "24",
"gender": "M"
}
- Example response
data:{ "hprIds": ["rajesh.kumar"] }
HPR search
These GET endpoints use the forwarded HPR token if a session is present, otherwise fall back to the ABDM gateway token — a session is preferred but not strictly required.
GET /v1/hpr/search/hprid-exists/:hprId
Plain English: username-availability check while choosing an HPR ID, independent of hpid-suggestions (a user may type a custom name that needs its own check).
- Response:
true/false.
GET /v1/hpr/search/mobile/:mobile
Plain English: look up a professional record by registered mobile number.
- Example response
data: the matching professional record(s).
GET /v1/hpr/search/hprid/:hprId
Plain English: look up a professional record by HPR ID.
- Example response
data: the matching professional record.
Master data (reference/lookup lists)
All GET unless noted, all read-only reference data proxied straight from NHPR's public master-data APIs — called with a bare, header-free request since these public endpoints reject Authorization/X-CM-ID headers. Used purely to populate dropdowns in the registration/onboarding UI:
| Endpoint | Purpose |
|---|---|
GET /v1/hpr/master/hpr-categories/:role |
Professional categories for a role (1=Doctor, 2=Nurse, 3=Other) |
GET /v1/hpr/master/hpr-subcategories/:role/:categoryCode |
Sub-categories under a category |
GET /v1/hpr/master/system-of-medicines |
Allopathy, Ayurveda, Homeopathy, Dentistry, Unani, etc. |
GET /v1/hpr/master/medical-councils |
State/national medical councils |
GET /v1/hpr/master/nurse-councils |
State/national nursing councils |
GET /v1/hpr/master/languages/:languageId |
Resolve one language record by id |
GET /v1/hpr/master/universities/:collegeId |
University affiliated with a college |
GET /v1/hpr/master/colleges/:stateId/:systemOfMedicine |
Colleges in a state teaching a given system of medicine |
POST /v1/hpr/master/courses |
Degree courses by college (loose passthrough body — the one master-data call that is a POST and goes through the standard authenticated client) |
GET /v1/hpr/master/countries |
Country list |
GET /v1/hpr/master/states |
Indian states/UTs |
GET /v1/hpr/master/districts/:stateId |
HPR's own district list (distinct code system from LGD, see below) |
GET /v1/hpr/master/sub-districts/:districtId |
HPR's own sub-district list |
GET /v1/hpr/master/affiliated-boards |
All academic boards |
GET /v1/hpr/master/affiliated-boards/:boardId |
One board's details |
GET /v1/hpr/master/affiliated-boards-state/:stateId |
Boards scoped to a state |
GET /v1/hpr/master/all-ministries |
Government ministries (used in facility ownership fields) |
Example response shape for any of the above (data): [{ "code": "1", "name": "Allopathy" }, { "code": "2", "name": "Ayurveda" }] — actual field names vary by list; all are passthrough of ABDM's own shape.
Facility (HFR) onboarding — the "HPR → HFR → Done" journey
What this is for: this is the "HPR → HFR → Done" journey — a doctor may need to register (or find and link to) the facility they work at in the national Health Facility Registry, as a 4-step wizard. A trackingId returned by step 1 must be threaded through every subsequent step of the same in-progress registration.
POST /v1/hpr/facility/search
Plain English: search for an existing facility (to link to, or to confirm one doesn't already exist before creating a new one).
- Request (
FacilitySearchRequestSchema):{ "facilityName": "Sahyadri Hospital", "stateLGDCode": "27", "districtLGDCode": "522", "page": 1, "resultsPerPage": 10 } - Example response
data:{ "facilities": [{ "facilityId": "IN2810000123", "facilityName": "Sahyadri Hospital", "state": "Karnataka" }], "total": 1 }
POST /v1/hpr/facility/basic-info (step 1)
- Request (
FacilityBasicInfoSchema):
{
"trackingId": "",
"facilityInformation": {
"facilityName": "Sahyadri Hospital",
"facilityAddressDetails": {
"stateLGDCode": "27",
"districtLGDCode": "522",
"pincode": "560001",
"addressLine1": "12 MG Road"
},
"facilityContactInformation": {
"facilityEmailId": "contact@sahyadri.example",
"facilityContactNumber": "9876543210"
},
"ownershipCode": "G",
"systemOfMedicineCode": "M",
"typeOfServiceCode": "IPD,OPD",
"facilityOperationalStatus": "F"
}
}
trackingId is empty on the first call; ABDM issues one in the response.
- Example response
data:{ "trackingId": "trk-fac-001" }
POST /v1/hpr/facility/additional-info (step 2)
- Request (
FacilityAdditionalInfoSchema):{ "trackingId": "trk-fac-001", "generalInformation": { "numberOfBeds": 120 } } - Example response
data:{ "trackingId": "trk-fac-001", "status": "SAVED" }
POST /v1/hpr/facility/detailed-info (step 3)
- Request (
FacilityDetailedInfoSchema):{ "trackingId": "trk-fac-001", "specialities": [{ "code": "CARDIOLOGY" }], "diagnosticServices": ["X-RAY", "MRI"] } - Example response
data:{ "trackingId": "trk-fac-001", "status": "SAVED" }
POST /v1/hpr/facility/submit (step 4)
- Request (
FacilitySubmitSchema):{ "trackingId": "trk-fac-001", "sourceOfInformation": "SELF", "facilitySuperUser": "91-1234-5678-9012" } - Example response
data:{ "facilityId": "IN2810000123", "status": "SUBMITTED" } - Next: Facility record enters the HFR registration pipeline; the resolved
facilityId(HFR ID) is what gets used elsewhere ashipId/hfrIdonce the facility goes live as a HIP.
GET /v1/hpr/facility/master-types and GET /v1/hpr/facility/master-data/:type
Plain English: reference data for the onboarding form's dropdowns (e.g. type=OWNER returns ownership codes).
- Example response
data:[{ "code": "G", "label": "Government" }, { "code": "P", "label": "Private" }]
POST /v1/hpr/facility/contact-details, POST /v1/hpr/facility/send-otp, POST /v1/hpr/facility/validate-otp
Plain English: a contact-verification sub-flow proving the caller has authority over the facility record before it's finalized.
contact-detailsrequest:{ "facilityId": "IN2810000123" }→ response: contact record on file.send-otprequest:{ "facilityId": "IN2810000123" }→ response:{ "transactionId": "txn-otp-321" }validate-otprequest (FacilityValidateOtpSchema):{ "facilityId": "IN2810000123", "sourceId": "src-1", "otp": "123456", "source": "MOBILE", "transactionId": "txn-otp-321" }→ response:{ "verified": true }
Cascading dropdown endpoints for onboarding
| Endpoint | Purpose |
|---|---|
POST /v1/hpr/facility/fetch-type |
Facility types for an ownership+system-of-medicine combo |
POST /v1/hpr/facility/fetch-subtype |
Sub-types for a facility type |
POST /v1/hpr/facility/owner-subtype |
Ownership sub-types (e.g. private-for-profit vs not-for-profit) |
POST /v1/hpr/facility/specialities |
Specialities for a system of medicine |
Example request/response for fetch-type (FacilityFetchTypeSchema): { "ownershipCode": "G", "systemOfMedicineCode": "M" } → [{ "code": "2", "label": "Hospital" }]
POST /v1/hpr/facility/deduplicate
Plain English: checks whether a near-duplicate facility already exists before finalizing a new registration.
- Request (
FacilityDeduplicateSchema):{ "name": "Sahyadri Hospital", "address": "12 MG Road", "district": "Bengaluru Urban", "subDistrict": "Bengaluru North" } - Example response
data:{ "duplicatesFound": false }
POST /v1/hpr/facility/multiple-hrp-services
Plain English: links one or more Health-Related Practitioner services to a facility record. The request/response shape here is intentionally left untyped (z.looseObject({})) since ABDM's contract for this endpoint isn't yet pinned down against a live sandbox — validate before hardening.
- Request: free-form JSON object (facility ID + service list, per ABDM's own shape).
- Example response
data:{ "status": "LINKED" }
LGD (Local Government Directory) geography
Separate code system from HPR's own master/districts/master/sub-districts — LGD codes are specifically what the facility address fields (stateLGDCode/districtLGDCode/subDistrictLGDCode) expect.
| Endpoint | Purpose |
|---|---|
GET /v1/hpr/lgd/states |
All LGD states |
GET /v1/hpr/lgd/districts/:stateCode |
LGD districts for a state |
GET /v1/hpr/lgd/subdistricts/:districtCode |
LGD sub-districts for a district |
Example response (data) for any: [{ "code": "522", "name": "Bengaluru Urban" }]