Skip to main content

Physical consultation

Physical consultation is the fullest service on UHI. A patient searches for a doctor, then sees real slots and fees from clinics they have no prior relationship with. They book one, and receive a 4-digit PIN to present at the clinic. After this page you will know the four stages, the endpoints each role exposes, and the fields on every call, as NHA documents them.

Read UHI services first, for the context block, the acknowledgement model, signing and the two transports.

Where this service stands

The full consultation lifecycle is the first phase, live and open for onboarding. Online payment and refunds are the second phase, in ideation. Today the only payment model is pay on visit.

Service identity

Every call in this service carries these fixed values.

FieldValue
context.domainnic2004:85111
context.core_version0.7.1
message.intent.fulfillment.typePhysical (case sensitive)
message.intent.item.descriptor.codeConsultation
message.intent.item.descriptor.nameConsultation

Who is involved

ActorRole here
EUAThe patient facing app. Searches, books, shows the PIN and the status.
HSPAThe provider platform. Holds doctor profiles and slots, confirms bookings, generates the PIN, drives the lifecycle.
HSPThe hospital, clinic or doctor. The HSPA is its digital interface.
GatewayThe routing layer. Involved in discovery only.
NHANetwork operator. Governs onboarding, compliance and the protocol.

The flow end to end

Stage by stage

StageCallsTransport
1. Discoverysearch and on_search, twiceFirst pair via the gateway, second pair direct
2. Bookinginit, on_init, confirm, on_confirmDirect
3. Fulfilmentstatus, on_status, on_updateDirect
4. Post-fulfilmentcancel, on_cancel, on_messageDirect

Endpoints you expose

An HSPA exposes these. Everything except the first search is called by an EUA directly.

EndpointCalled byWhat you do
/search (first)Gateway broadcast, with X-Gateway-AuthorizationQuery your doctor catalog, answer via on_search
/search (second)EUA, directReturn slots for the selected doctor
/initEUA, directHold the slot, answer with terms via on_init
/confirmEUA, directConfirm the appointment, return the PIN via on_confirm
/statusEUA, directReturn the current order state via on_status
/cancelEUA, directProcess the cancellation, answer via on_cancel
/on_updateEUA, directReceive a DOCTOR_NO_SHOW state from the EUA
/on_messageEUA, directReceive a chat message or file from the patient

An EUA exposes these. Everything except the first on_search is called by the HSPA directly.

EndpointCalled byWhat you do
/on_search (first)Gateway, with X-Gateway-AuthorizationAggregate catalogs, store each provider_uri
/on_search (second)HSPA, directShow the slots for the chosen doctor
/on_initHSPA, directShow all terms to the patient, store order.id
/on_confirmHSPA, directShow the PIN, store the order
/on_statusHSPA, directReplace your stored order state
/on_updateHSPA, directUpdate state and notify the patient
/on_cancelHSPA, directMark the appointment cancelled
/on_messageHSPA, directShow the message from the provider. Mandatory for an EUA

Stage 1: discovery

First search, broadcast

Your EUA posts to the gateway. The gateway validates your signature and the domain, then forwards to every registered HSPA in that domain. Search filters go in message.intent.

POST https://uhigatewaysandbox.abdm.gov.in/api/v1/uhi/search
FieldTypeRequiredWhat it is
fulfillment.typestringYesPhysical. Case sensitive
fulfillment.agent.namestringNoDoctor name, for a name search
fulfillment.agent.idstringNoDoctor HPR address, for example drmehra@hpr.ndhm
fulfillment.start.time.timestampdatetimeYesStart of the search window
fulfillment.end.time.timestampdatetimeYesEnd of the search window
item.descriptor.codestringYesConsultation
item.descriptor.namestringYesConsultation
category.descriptor.codestringNoSpeciality code, for example CARDIOLOGY
category.descriptor.namestringNoSpeciality name, for example Cardiology
location.gpsstringConditionallatitude,longitude for a proximity search
location.radius.typestringConditionalCONSTANT when using GPS
location.radius.valuestringConditionalRadius in km, for example "10"
location.radius.unitstringConditionalkm
location.city.namestringNoCity name
location.city.codestringNoCity STD code
address.area_codestringNo6-digit pincode
provider.descriptor.namestringNoFacility name
provider.idstringNoProvider ID, used in the second search

A search by state and district:

{
"context": {
"action": "search",
"city": "std:011",
"consumer_id": "<YOUR_EUA_ID_FROM_NHA_ONBOARDING>",
"consumer_uri": "<YOUR_HTTPS_CALLBACK_URL>",
"core_version": "0.7.1",
"country": "IND",
"domain": "nic2004:85111",
"message_id": "e9a19230-f951-11ec-b135-53aea776f66b",
"timestamp": "2026-06-18T06:52:13.969464Z",
"transaction_id": "e9a19230-f951-11ec-b135-53aea776f66b"
},
"message": {
"intent": {
"fulfillment": {
"type": "Physical",
"start": { "time": { "timestamp": "2026-06-18T10:37:32" } },
"end": { "time": { "timestamp": "2026-06-18T23:59:59" } }
},
"item": {
"descriptor": { "code": "Consultation", "name": "Consultation" }
},
"location": {
"state": { "name": "MAHARASHTRA", "code": "27" },
"district": { "name": "PUNE", "code": "490" }
}
}
}
}

Three warnings apply to this call:

  • A GPS search needs all three radius fields. Omit any one and the GPS filter is ignored without an error.
  • The transaction_id in search must match the one in the on_search that answers it, or you cannot correlate the response.
  • More filter combinations exist. The Swagger spec is the list.

First on_search, the catalog

Each matching HSPA answers independently, so you receive several. Aggregate them on transaction_id.

FieldRequiredWhat it is
context.provider_uriYesThe HSPA base URL. Store this. Every later call goes here
message.catalog.descriptor.nameYesHSPA name
catalog.providers[].idYesProvider or hospital ID within this HSPA
catalog.providers[].descriptor.nameYesHospital or clinic name
catalog.providers[].categories[].descriptor.nameYesSpeciality name
catalog.providers[].categories[].descriptor.codeYesSpeciality code
catalog.providers[].fulfillments[].idYesSlot UUID. This becomes fulfillment.id in init
catalog.providers[].fulfillments[].typeYesPhysical
catalog.providers[].fulfillments[].agent.idYesDoctor HPR ID
catalog.providers[].fulfillments[].agent.nameYesDoctor's registered name
catalog.providers[].fulfillments[].agent.genderNoM or F
catalog.providers[].fulfillments[].agent.tagsNo@abdm/gov.in/experience, /languages, /education, /hpr_id, /hfr_id, /hip_id
catalog.providers[].fulfillments[].start.time.timestampYesSlot start
catalog.providers[].fulfillments[].end.time.timestampYesSlot end
catalog.providers[].items[].idYesItem ID. This becomes order.item.id in init
catalog.providers[].items[].price.valueYesFee in INR, as a decimal string
catalog.providers[].items[].fulfillment_idYesLinks the item to its slot
catalog.providers[].location.gpsNoProvider coordinates
catalog.providers[].location.addressNoProvider street address

Trimmed to one provider:

{
"context": {
"domain": "nic2004:85111",
"action": "on_search",
"consumer_id": "<YOUR_EUA_ID_FROM_NHA_ONBOARDING>",
"provider_id": "hspa-nha",
"provider_uri": "https://hspasbx.abdm.gov.in/api/v1/hspa",
"transaction_id": "a1b2c3d4-f951-11ec-b135-53aea776f66b",
"message_id": "b2c3d4e5-f951-11ec-b135-53aea776f66b"
},
"message": {
"catalog": {
"descriptor": { "name": "ABDM Reference HSPA" },
"providers": [
{
"id": "1",
"descriptor": { "name": "Safdarjung Medical Centre" },
"categories": [
{ "id": "201", "parent_category_id": "101", "descriptor": { "name": "Cardiology", "code": "CARDIOLOGY" } },
{ "id": "101", "descriptor": { "name": "Allopathy", "code": "ALLOPATHY" } }
],
"fulfillments": [
{
"id": "slot-uuid-a1b2c3d4-abcd-1234-efgh-567890abcdef",
"type": "Physical",
"agent": {
"id": "priyamehra@hpr.ndhm",
"name": "Dr. Priya Mehra",
"gender": "F",
"tags": {
"@abdm/gov.in/experience": "8.0",
"@abdm/gov.in/languages": "Hindi, English",
"@abdm/gov.in/education": "MBBS, MD Cardiology",
"@abdm/gov.in/hpr_id": "73-5232-1888-8686"
}
},
"start": { "time": { "timestamp": "2026-04-16T10:00:00" } },
"end": { "time": { "timestamp": "2026-04-16T10:20:00" } }
}
],
"items": [
{
"id": "0",
"descriptor": { "name": "Consultation", "code": "CONSULTATION" },
"price": { "currency": "INR", "value": "500.0" },
"fulfillment_id": "slot-uuid-a1b2c3d4-abcd-1234-efgh-567890abcdef"
}
],
"location": {
"gps": "28.635308,77.224960",
"address": "Safdarjung Enclave, New Delhi 110029",
"city": { "name": "Delhi", "code": "011" }
}
}
]
}
}
}

An HSPA sends the full agent.tags set including @abdm/gov.in/hip_id. That ID is what lets records generated at the visit be pulled later.

Second search, direct

After the patient picks a doctor, your EUA sends a second search straight to the HSPA's provider_uri, asking for that doctor's slots in a time window. It carries provider_id and provider_uri in the context, and echoes the provider, fulfillments and items blocks from the first on_search. The HSPA answers with a second on_search scoped to that doctor.

Stage 2: booking

init

Direct to the HSPA. You send the patient's details and the chosen slot. The HSPA holds the slot temporarily.

FieldRequiredWhat it is
order.provider.idYesProvider ID from the catalog
order.item.idYesItem ID from the catalog
order.item.descriptor.codeYesConsultation
order.item.descriptor.nameYesConsultation
order.item.price.currencyNoINR
order.item.price.valueNoFee as a decimal string
order.item.fulfillment_idYesSlot UUID from the catalog
order.fulfillment.idYesThe same slot UUID
order.fulfillment.typeYesPhysical
order.fulfillment.agent.idYesDoctor HPR ID
order.fulfillment.agent.nameYesDoctor's registered name
order.fulfillment.start.time.timestampYesSlot start
order.fulfillment.end.time.timestampYesSlot end
order.fulfillment.tagsConditional@abdm/gov.in/slot_id is mandatory and holds the slot UUID
order.billing.nameYesPatient billing name
order.billing.addressYesObject with door, name, locality, city, state, country, area_code
order.billing.phoneYes10-digit contact number
order.billing.emailNoPatient email
order.customer.idYesPatient ABHA address, for example rahul.k001@sbx
order.customer.person.genderNoM or F
order.customer.person.dobNoYYYY-MM-DD
order.payment.typeYesON-ORDER for pay on visit. Other values: PRE-FULFILLMENT, ON-FULFILLMENT
order.payment.params.redirect_urlNoPayment callback URL
{
"context": {
"domain": "nic2004:85111",
"country": "IND",
"city": "std:011",
"action": "init",
"core_version": "0.7.1",
"consumer_id": "<YOUR_EUA_ID_FROM_NHA_ONBOARDING>",
"consumer_uri": "<YOUR_HTTPS_CALLBACK_URL>",
"provider_id": "hspa-nha",
"provider_uri": "https://hspasbx.abdm.gov.in/api/v1/hspa",
"transaction_id": "a1b2c3d4-f951-11ec-b135-53aea776f66b",
"message_id": "d4e5f6a7-32af-11ef-bcbe-590b07ce8c90",
"timestamp": "2026-04-15T09:10:00Z"
},
"message": {
"order": {
"provider": { "id": "1" },
"item": {
"id": "0",
"descriptor": { "name": "Consultation", "code": "CONSULTATION" },
"price": { "currency": "INR", "value": "500.0" },
"fulfillment_id": "slot-uuid-a1b2c3d4-abcd-1234-efgh-567890abcdef"
},
"fulfillment": {
"id": "slot-uuid-a1b2c3d4-abcd-1234-efgh-567890abcdef",
"type": "Physical",
"agent": { "id": "priyamehra@hpr.ndhm", "name": "Dr. Priya Mehra" },
"start": { "time": { "timestamp": "2026-04-16T10:00:00" } },
"end": { "time": { "timestamp": "2026-04-16T10:20:00" } }
},
"billing": {
"name": "Rahul Kumar Sharma",
"address": {
"door": "B-204",
"name": "Rahul Kumar Sharma",
"locality": "Rohini Sector 14",
"city": "Delhi",
"state": "Delhi",
"country": "INDIA",
"area_code": "110085"
},
"phone": "9876543210",
"email": "rahul.sharma@email.com"
},
"customer": {
"id": "rahul.k001@sbx",
"person": { "gender": "M", "dob": "1990-05-15", "dayOfBirth": 15, "monthOfBirth": 5, "yearOfBirth": 1990 }
},
"payment": {
"type": "ON-ORDER",
"params": { "redirect_url": "<YOUR_PAYMENT_REDIRECT_URL>" }
}
}
}
}

Two warnings:

  • fulfillment.id must be exactly the slot UUID from on_search. A mismatch makes the HSPA reject the call or fail to hold the slot without telling you.
  • The hold is short, typically 15 minutes. If confirm does not arrive in time the slot is released and you start again at init.

on_init, the terms

The HSPA answers with the order ID, an itemised quote, and five term objects the patient has to accept.

Field added by the HSPAWhat it is
order.idThe HSPA's order ID, generated here. Send it in every later call. Use an alphanumeric string, for example AHS12345
order.terms[]Five term objects, each with termsState: "INITIATED"
order.quotePrice breakup: consultation, SGST, CGST, registration
order.payment.typeON-ORDER, FREE or PRE-ORDER
order.payment.statusNOT_PAID or FREE

Each term object:

FieldRequiredWhat it is
terms[].typeYesCommercial, Settlement, Cancellation, Refund or Payment
terms[].descriptor.nameYesTerm title
terms[].descriptor.short_descNoBrief description
terms[].descriptor.long_descNoFull text. Show this to the patient
terms[].reasonRequiredYesIf true, a reason is needed when this term is actioned
terms[].timePeriodYesValidity. Copy unchanged into confirm
terms[].reasonConditionalRequired in confirm when reasonRequired is true
terms[].termsStateYesINITIATED here. You set AGREED in confirm

Store every term object exactly as received. Change only termsState, and reason where it is required.

confirm

Send the whole order back with every term at AGREED. Any term still at INITIATED and the HSPA rejects the call. Use the order.id the HSPA assigned in on_init, not any ID you generated.

on_confirm, the PIN

The HSPA sets order.state to CONFIRMED and returns a 4-digit PIN.

FieldWhat it is
order.stateCONFIRMED, or FAILED on a payment or system error
order.idThe HSPA's order ID
order.authorization.typePIN
order.authorization.tokenThe 4-digit PIN
order.authorization.valid_fromPIN validity start
order.authorization.valid_toPIN validity end, usually end of the appointment day
order.authorization.statusGENERATED here
order.fulfillment.tags.@abdm/gov.in/slot_idThe confirmed slot UUID
{
"authorization": {
"type": "PIN",
"token": "3774",
"valid_from": "2026-06-18T00:00:00",
"valid_to": "2026-06-18T23:59:00",
"status": "GENERATED"
}
}

The PIN is security sensitive. Hold it in memory or secure session storage on the EUA. Do not write it to a database or to application logs.

The HSPA has two more obligations here. It sends an exact copy of the on_confirm payload to the gateway audit endpoint listed under gateway endpoints, and it sets the communication tags on the fulfilment:

{
"tags": {
"@abdm/gov.in/slot_id": "79db6b5b-afe4-4297-b9b1-5148ed45372c",
"@abdm/gov.in/messaging_support": "true",
"@abdm/gov.in/deep_link": "",
"@abdm/gov.in/helpline_number": "",
"@abdm/gov.in/chatbot_link": ""
}
}

messaging_support and a helpline number are mandatory in on_confirm.

Stage 3: fulfilment

status and on_status

status carries only order.id. The HSPA answers with the full order object, and you replace your stored state with it. This is reconciliation, not a polling loop.

Order states

StateSet byMeaning
CONFIRMEDHSPA, in on_confirmBooked. PIN generated
APPOINTMENT_STARTEDHSPA, in on_updateThe doctor has begun the consultation
COMPLETEDHSPA, in on_updateThe doctor has marked the consultation complete
CANCELLEDHSPA or EUACancelled under the agreed terms
NO_SHOWHSPA, in on_updateThe patient did not appear
DOCTOR_NO_SHOWEUA, in on_update to the HSPAThe doctor did not appear
FAILEDHSPA, in on_confirmPayment or system failure at confirmation

DOCTOR_NO_SHOW is the only state an EUA may set. Everything else is the HSPA's.

PIN states

StateMeaning
GENERATEDSet when the PIN is created at confirmation
VERIFIEDThe provider checked the PIN before the consultation
HSPA_OVERRIDEThe PIN was not checked and the HSPA overrode the check

on_update

The HSPA pushes state changes to your /on_update as they happen. This is the main real-time channel, not status. Update your local state and notify the patient.

Stage 4: post-fulfilment

cancel and on_cancel

FieldRequiredWhat it is
order.idYesThe order to cancel
order.stateYesCANCELLED
order.fulfillment.tags.@abdm/gov.in/cancelledbyYespatient or doctor
{
"message": {
"order": {
"id": "0415-234567-8901",
"state": "CANCELLED",
"fulfillment": {
"tags": { "@abdm/gov.in/cancelledby": "patient" }
}
}
}
}

The cancelledby tag is mandatory. Without it the HSPA cannot tell which set of cancellation terms applies.

Your /on_cancel handler has to cope with both directions: the HSPA's acknowledgement of a patient cancellation, and an HSPA-initiated cancellation when a doctor cancels. Read the cancelledby tag to tell them apart.

on_message

Optional for an HSPA, mandatory for an EUA. Both sides consume the same shape. Content sits under message.intent.chat.

FieldRequiredWhat it is
chat.sender.person.idYesABHA address of the sender, or HPR ID when the doctor sends
chat.sender.person.nameYesSender's name
chat.receiver.person.idYesHPR ID of the doctor, or ABHA address of the patient
chat.receiver.person.nameYesReceiver's name
chat.content.content_idYesUUID for this message
chat.content.content_valueYesBase64-encoded text or file
chat.content.content_typeYestext or media
chat.content.content_mimeTypeConditionalMIME type when content_type is media
chat.content.content_fileNameConditionalFile name when content_type is media
chat.content.hiTypeConditionalHealth information type, for example prescription, labReport
chat.time.timestampYesMessage time

Gateway endpoints

You do not build these. NHA operates them.

EndpointCalled byPurpose
POST /api/v1/uhi/searchEUABroadcast a search to all registered HSPAs
POST /api/v1/uhi/on_searchHSPADeliver a catalog, which the gateway forwards to the EUA
POST /api/v1/uhi/on_confirm_auditHSPAExact copy of every on_confirm
POST /api/v1/uhi/on_update_auditHSPAExact copy of every on_update. The care context ID goes here
POST /api/v1/uhi/on_cancel_auditHSPAExact copy of every on_cancel
POST /api/v1/uhi/on_status_auditHSPAExact copy of every on_status
POST /api/v1/networkregistry/lookupEitherLook up a counterparty's public key

The audit copies are an HSPA obligation for compliance traceability, not optional.

Cancellation reason codes

The reason codes are fixed. You choose the labels your users see.

Patient-initiated, sent in cancel with cancelledby: patient

CodeMeaning
PATIENT_PERSONAL_EMERGENCYPatient or family emergency
PATIENT_HEALTH_IMPROVEDCondition resolved, consultation no longer needed
PATIENT_UNABLE_TO_VISIT_PHYSICALLYScheduling conflict or inability to reach the facility
DOCTOR_ASKED_TO_CANCELThe doctor asked the patient to cancel
PATIENT_BOOKED_IN_ERRORWrong doctor, speciality, date or time
PATIENT_SEEKING_ALTERNATIVEPatient has decided to see someone else
PATIENT_OTHERAnything else. Your EUA must capture free text

Doctor or facility-initiated, sent in on_cancel with cancelledby: doctor

CodeMeaning
DOCTOR_PERSONAL_EMERGENCYUnplanned personal or medical emergency
DOCTOR_UNAVAILABLEUnexpected surgery, patient emergency or high footfall
DOCTOR_SCHEDULE_CHANGESession timings changed
FACILITY_CLOSUREFacility temporarily closed
TECHNICAL_SYSTEM_ISSUEHSPA platform failure or downtime
DOCTOR_OTHERAnything else. The HSPA must provide free text

PIN override, used by facility staff

CodeWhen it applies
OVERRIDE_EMERGENCY_CONSULTATIONPatient arrives in acute distress
OVERRIDE_PIN_TECH_FAILUREThe app cannot show the PIN. Identity verified another way
OVERRIDE_PIN_DELIVERY_FAILUREThe PIN never reached the patient
OVERRIDE_VULNERABLE_PATIENTElderly, differently-abled or low digital literacy patient
OVERRIDE_EUA_OUTAGEThe EUA platform is down
OVERRIDE_MISMATCHPIN could not be validated after three attempts
OVERRIDE_OTHERAnything else. The HSPA must provide free text

Terms and conditions text

The on_init terms array carries the text the patient reads before confirming. Sample clauses are published. Two points carry through all of them.

  • UHI is a technology gateway. It does not supervise providers, does not guarantee outcomes, and does not collect, hold or route any payment.
  • Every payment, refund, cancellation charge and pricing dispute is between the patient and the facility.

Field and error reference

The tables above carry the fields you send and receive most. For the complete order field list, use the Swagger spec.

Error codes for this service are not published as a list. The error object carries type and code, both mandatory, with path and message optional.

Next