Last updated: 12 October 2026
All endpoints are called with an Authorization: Bearer <idToken> header — except
/auth_token, which issues that token.
https://asia-south1-psychic-city-328609.cloudfunctions.net/<endpoint>
/discharge_patient is PUT./health_document is multipart/form-data (POST).Every error response uses the same JSON shape. There are no other error shapes.
{
"message": "Human-readable summary of the failure",
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{ "field": "patient.abhaAddress", "message": "The direct input of Abha Address or Abha number is not possible." }
]
}
}
| Field | Always present? | Meaning |
|---|---|---|
message | Yes | Top-level summary (kept for backward compatibility — safe to keep parsing it). |
error.code | Yes | Stable, machine-readable code. Match on this. |
error.message | Yes | Curated, human-readable message. |
error.details | No — only on VALIDATION_ERROR | Array of { field, message }, one entry per failed field. |
Example — non-validation error
{
"message": "Patient not found",
"error": {
"code": "PATIENT_NOT_FOUND",
"message": "Patient not found"
}
}
Every response — success or error — includes the same headers.
x-request-idx-request-id header, its value is echoed back unchanged.Send an x-request-id on every request and log the request/response pair against it. When
escalating an issue to Nice HMS support, include this value — it correlates to the
server-side log of the full (unredacted) error.
Access-Control-Allow-OriginEvery response includes Access-Control-Allow-Origin: *, so the API can be called
directly from browser-based clients (CORS is enabled for all origins). No per-origin
allow-list is required on the integrator's side.
Match on error.code — these are stable strings.
error.code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Request body failed schema validation. See error.details for field-level reasons. Also used for an unsupported upload type, a missing file, or a PDF combined with images on health_document. |
VALIDATION_ERROR | 413 | An uploaded file exceeds the 5 MB limit (health_document). |
INVALID_EMAIL_FORMAT | 400 | An email string is malformed. |
INVALID_DOCTOR_GCP_FHIR_ID | 400 | A doctorGcpId does not exist in the FHIR store (Practitioner). |
DOCTOR_DETAILS_REQUIRED | 400 | doctorDetails is missing (health_document). |
INVALID_REQUEST_METHOD | 400 | Wrong HTTP method for the endpoint. |
UNAUTHORIZED | 401 | Missing/invalid/expired token, or the token is not mapped to an organization. |
SUBSCRIPTION_EXPIRED | 403 | The organization's subscription is invalid or expired. |
PATIENT_NOT_FOUND | 404 | No patient matches the supplied patientId / abhaAddress for this organization. |
ENCOUNTER_NOT_FOUND | 404 | A supplied encounterId was not found for this patient/organization. |
NO_ACTIVE_ENCOUNTER | 409 | A document endpoint resolved no encounter: the patient has no encounter at all (not even a finished one). Create a visit with opd_patient / admit_patient first. |
DOCTOR_NOT_FOUND | 404 | The Practitioner FHIR id does not exist (edit_doctor). |
SCAN_TOKEN_NOT_FOUND | 404 | No scanned profile for this token under your facility (scan_and_share) — unknown token, a token minted for another facility, or a share body with no patient demography. |
SCAN_TOKEN_EXPIRED | 410 | The Scan-and-Share token is older than 48 hours (scan_and_share). Ask the patient to scan the QR code again. |
EMAIL_ALREADY_EXISTS | 409 | Duplicate email when creating a doctor. |
ABDM_GATEWAY_ERROR | 502 | Upstream ABDM gateway returned an error (e.g. OTP verification failed, auth mode unavailable). |
LINK_TOKEN_MISSING | 502 | ABDM care-context link token was not found after the callback — a genuine failure, not a rate limit. |
LINK_TOKEN_RATE_LIMITED | 429 | ABDM refused to issue a link token because its cap of 3 new link tokens per ABHA address per 24 hours was reached. Reuse the existing token (valid ~6 months) and retry after 24 hours. |
DB_ERROR | 500 | A database operation failed. Curated generic message. |
INTERNAL_ERROR | 500 | Any unhandled / unknown error. Curated generic message. |
| Status | Category |
|---|---|
| 200 | Success |
| 400 | Bad request / validation |
| 401 | Unauthorized |
| 403 | Forbidden (subscription) |
| 404 | Not found |
| 409 | Conflict (duplicate email / no active encounter) |
| 410 | Gone (Scan-and-Share token expired) |
| 413 | Payload too large (uploaded file exceeds the 5 MB limit) |
| 429 | Rate limited (ABDM link-token daily cap reached) |
| 500 | Internal server error |
| 502 | Bad gateway (ABDM upstream) |
400 VALIDATION_ERROR first (auth is checked after body
validation).details[].field use dot notation, e.g. patient.firstName,
doctorDetails.0.doctorGcpId, patient.dob.x-request-id.Some VALIDATION_ERROR messages name the rejected field but not its accepted values —
e.g. details[].message is just "Invalid category". The accepted values for each enum
field are listed in that endpoint's request table; see
ABDM API — Documents.
For example, /diagnostic_report category accepts:
Hematology·Biochemistry·Microbiology·Radiology·Others
The following codes are present on every authenticated route:
VALIDATION_ERROR(400) ·UNAUTHORIZED(401) ·SUBSCRIPTION_EXPIRED(403) ·DB_ERROR(500) ·INTERNAL_ERROR(500)
Each endpoint's documentation lists these shared codes + its own additional codes.
Where a route says "patientNeeds", the body must include at least one of abhaAddress
(string) or patientId (number). Omitting both yields 400 VALIDATION_ERROR with the
message "Either abhaAddress or patientId is required".
The table lists each route's additional codes — the ones on top of the shared codes.
(/auth_token is public and skips the subscription check.)
| Endpoint | Additional error codes |
|---|---|
/auth_token (public) | VALIDATION_ERROR (400) · UNAUTHORIZED (401) |
/create_patient | — |
/create_doctor | INVALID_EMAIL_FORMAT (400) · EMAIL_ALREADY_EXISTS (409) |
/edit_doctor | DOCTOR_NOT_FOUND (404) |
/get_doctor_opd_patients_by_date | — |
/get_doctor_ipd_patients_by_date | — |
/patient_by_id | PATIENT_NOT_FOUND (404) |
/patient_by_abhaAddress | PATIENT_NOT_FOUND (404) |
/scan_and_share | SCAN_TOKEN_NOT_FOUND (404) · SCAN_TOKEN_EXPIRED (410) |
/patient_register_abha_otp_init | ABDM_GATEWAY_ERROR (502) |
/patient_register_abha_otp_confirm | ABDM_GATEWAY_ERROR (502) |
/opd_patient | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) |
/admit_patient | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) |
/discharge_patient | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) |
/discharge_summary | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) · NO_ACTIVE_ENCOUNTER (409) · ABDM_GATEWAY_ERROR (502) · LINK_TOKEN_MISSING (502) · LINK_TOKEN_RATE_LIMITED (429) |
/diagnostic_report | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) · NO_ACTIVE_ENCOUNTER (409) · ABDM_GATEWAY_ERROR (502) · LINK_TOKEN_MISSING (502) · LINK_TOKEN_RATE_LIMITED (429) |
/op_consultation | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) · NO_ACTIVE_ENCOUNTER (409) · ABDM_GATEWAY_ERROR (502) · LINK_TOKEN_MISSING (502) · LINK_TOKEN_RATE_LIMITED (429) |
/procedure_ot_notes | INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) · NO_ACTIVE_ENCOUNTER (409) · ABDM_GATEWAY_ERROR (502) · LINK_TOKEN_MISSING (502) · LINK_TOKEN_RATE_LIMITED (429) |
/health_document | INVALID_REQUEST_METHOD (400) · DOCTOR_DETAILS_REQUIRED (400) · INVALID_DOCTOR_GCP_FHIR_ID (400) · PATIENT_NOT_FOUND (404) · ENCOUNTER_NOT_FOUND (404) · NO_ACTIVE_ENCOUNTER (409) · ABDM_GATEWAY_ERROR (502) · LINK_TOKEN_MISSING (502) · LINK_TOKEN_RATE_LIMITED (429) |
/uhi and /uhi/whatsapp follow the UHI protocol (search/init/confirm/cancel/status),
which has its own acknowledgement and error format — documented separately, not via this
envelope.| You did this | You'll see |
|---|---|
| Missing/invalid token | 401 UNAUTHORIZED |
| Expired subscription | 403 SUBSCRIPTION_EXPIRED |
| Bad field / wrong enum / missing required field | 400 VALIDATION_ERROR (+ details) |
A file over 5 MB on health_document | 413 VALIDATION_ERROR |
An unsupported file type, no file, or a PDF combined with images on health_document | 400 VALIDATION_ERROR |
A file on health_document you expected to reach ABDM, but status isn't final | 200, with abdmNotified: false |
Wrong doctorGcpId | 400 INVALID_DOCTOR_GCP_FHIR_ID |
Unknown patientId / abhaAddress | 404 PATIENT_NOT_FOUND |
Unknown encounterId, or one belonging to another patient/org | 404 ENCOUNTER_NOT_FOUND |
A tokenNumber with no scanned profile, or one minted for another facility | 404 SCAN_TOKEN_NOT_FOUND |
A tokenNumber from a scan more than 48 hours ago | 410 SCAN_TOKEN_EXPIRED |
| A document endpoint where the patient has no encounter at all (not even finished) | 409 NO_ACTIVE_ENCOUNTER |
Duplicate email on create_doctor | 409 EMAIL_ALREADY_EXISTS |
ABDM OTP/search/verify failure — including patient_register_abha_otp_init (ABHA address search / OTP request) and patient_register_abha_otp_confirm (OTP verification) | 502 ABDM_GATEWAY_ERROR (its error.message is ABDM's own reason) |
| ABDM link token missing after callback (genuine failure) | 502 LINK_TOKEN_MISSING |
| A 4th+ link-token generate for the same ABHA within 24 hours | 429 LINK_TOKEN_RATE_LIMITED |
| Anything else unexpected | 500 INTERNAL_ERROR / 500 DB_ERROR |
PATIENT_NOT_FOUND (404)PATIENT_NOT_FOUND means no patient matched the identifier you sent for the organization
tied to your bearer token. Common causes, in order of likelihood:
patientId sent as a string, not a number. patientId must be a JSON number —
1250, not "1250". A quoted value can fail the lookup even when the digits look right.abhaAddress is a placeholder, not a real address. A literal value like
"{{abhaAddress}}" (an unresolved template variable) matches no patient. Send the actual
ABHA address (e.g. savitribilagi@sbx), or omit abhaAddress entirely when using
patientId./create_patient) has an empty abhaAddress — it can only be fetched by patientId,
not by ABHA address. Conversely an ABHA patient is fetched by abhaAddress.abhaAddress can't mask a valid patientId.Confirm the identifier before creating a visit by fetching it with /patient_by_id or
/patient_by_abhaAddress. If it resolves there but a visit endpoint still returns
PATIENT_NOT_FOUND, escalate with the x-request-id — the server log holds the full
unredacted cause.
LINK_TOKEN_RATE_LIMITED 429 / LINK_TOKEN_MISSING 502)Document endpoints file a care context against an ABDM link token before pushing. ABDM issues at most 3 new link tokens per ABHA address per 24 hours; a token is valid about 6 months and supports unlimited document sends. So a token should be generated once and reused, not re-created per document.
| What you see | What it means | What to do |
|---|---|---|
429 LINK_TOKEN_RATE_LIMITED | The 3-per-24 h cap was reached for this ABHA. ABDM refused to issue another token. | Do not retry within the same 24 h window. Reuse the existing token — if you hold one, send it; otherwise wait and retry after 24 h. |
502 LINK_TOKEN_MISSING | No token was found after the generateToken callback. A genuine failure (or a callback that had not yet landed). | Retry once after a short delay; if it persists, escalate with the x-request-id. |
Guidance:
429 is not a payload problem. Nothing in your request body caused it, so changing
fields won't help.401 UNAUTHORIZED401 means the bearer token is missing, malformed, expired, or not mapped to an organization.
Authorization: Bearer <idToken> — the literal word Bearer, one
space, then the token./auth_token again with your email/password and retry.401./auth_token. If /auth_token itself returns 401, the email or
password is wrong — verify them or request a reset (see
Getting integration credentials).400 VALIDATION_ERROR400 means the request body failed schema validation. error.details names each failed field
as { field, message } (dot notation, e.g. patient.dob). Common causes:
date on a visit/document endpoint, doa on
/admit_patient, dod on /discharge_patient, or patient.dob on /create_patient. The
message is usually "Required".patientId must be a JSON number (1250, not "1250")."{{abhaAddress}}" sent instead of a real
value fails validation./health_document — unsupported type, missing file, or a PDF combined
with images (400); a file over 5 MB (413).Validation runs before authentication, so a request that is both unauthenticated and
malformed returns 400, not 401.
500 DB_ERROR / 500 INTERNAL_ERRORBoth are server-side failures. Nothing in your request body caused them, so changing fields will not help.
500 up to 2–3 times with exponential backoff (for
example 1s, 2s, 4s).400, 401, 403, 404, 409, 410, or 413 — those need a fix, not a
retry.x-request-id. If a 500 persists, send the request's x-request-id to
Nice HMS support (admin@nicehms.com) — it maps to the full unredacted server-side log.An enum VALIDATION_ERROR names the rejected field but not its accepted values. Accepted
values live in each endpoint's request table:
| Field | Accepted values |
|---|---|
patient.gender | Male · Female · Other |
patient.prefix | Mr · Mrs · Ms · Dr · NA · Master · Er |
authMode (patient_register_abha_otp_init) | MOBILE_OTP · AADHAAR_OTP · DONT_KNOW |
authMode (patient_register_abha_otp_confirm) | MOBILE_OTP · AADHAAR_OTP |
/diagnostic_report category | Hematology · Biochemistry · Microbiology · Radiology · Others |
Timestamp fields use ISO 8601 — e.g. "2023-05-08T19:21:23.918Z" (see the examples in
each endpoint). patient.dob is the exception: YYYY-MM-DD (date only, no time).
| Field | Endpoint(s) | Required |
|---|---|---|
date | /opd_patient, /discharge_summary, /op_consultation, /procedure_ot_notes | Yes |
date | /diagnostic_report, /health_document | No (defaults to request time) |
doa | /admit_patient | Yes |
dod | /discharge_patient | Yes |
patient.dob | /create_patient | Yes |
A VALIDATION_ERROR of { "field": "date", "message": "Required" } means you hit one of
the endpoints in the first row and omitted date.