NPS Zaruri Hai
Technical document

NPS Swasthya – HBA API Contract

Integration contract for Health Benefit Administrators (HBAs)

The eligibility, balance and withdrawal API contract every Health Benefit Administrator (HBA) implements to check NPS Swasthya eligibility, read the eligible balance and raise a withdrawal through NPS Central. It also fixes the common transport, security, data and error standards.

Version
Version 1.0
Status
Draft
Last updated
Updated 21/10/2026
Audience:
Health Benefit Administrators (HBAs)
Prepared by:
NPS Central technology team
Document control
Document status
For review by the NPS Central technology team, then for issue to each HBA
Applies to
NPS Central and every Health Benefit Administrator (HBA) that checks NPS Swasthya eligibility, reads the eligible balance or raises an NPS Swasthya withdrawal through NPS Central
Prepared by
NPS Central technology team
Date
21-Oct-2026

1Common Integration Architecture and Principles

An NPS Swasthya subscriber pays hospital bills from the corpus of their NPS Swasthya scheme. The Health Benefit Administrator (HBA) named on the subscriber's cover services the claim. To do that it needs to know whether the subscriber can withdraw, how much is available, and to raise the withdrawal. Each of those is held by the Central Recordkeeping Agency (CRA) that keeps the subscriber's PRAN.

Each CRA publishes its own Swasthya APIs, with its own transport, keys, encryption and response codes. NPS Central has built an adapter for each. An HBA integrates once, with NPS Central. It sends a PRAN; NPS Central finds the CRA holding that PRAN, calls it, and returns its answer in one standard shape. The HBA never calls a CRA directly and never holds CRA credentials.

ComponentResponsibility
HBAAPI consumer. Holds the claim (amount, OPD/IPD), relays the recordkeeper's OTPs from the subscriber, keeps the access_key safe, polls withdrawals to their outcome, and reconciles against the CRA acknowledgement.
NPS CentralAPI provider. Authenticates the HBA, finds the CRA holding the PRAN, runs the CRA's enrolment, eligibility, OTP and withdrawal calls, protects the CRA's balance credential, classifies outcomes and keeps the audit trail.
CRASystem of record for the PRAN, the NPS Swasthya corpus and the withdrawal. Sends every OTP to the mobile number registered against the PRAN, validates it, and acknowledges the withdrawal.
NPS Central HBA API StandardCommon interface, data, security and error requirements applicable to all HBAs (this document).

1.1One contract, every recordkeeper

This document is a provider specification: NPS Central hosts every API in it and the HBA calls them. The paths, request and response bodies, status values and error codes are the same whichever CRA holds the PRAN. Differences between the CRAs (transport, encryption, response codes and wording) are absorbed by NPS Central. CRA names and CRA-native error codes never appear in a response; where a CRA's own sentence is passed through, its name is replaced with "the recordkeeper".

Field names are the CRAs' own. CAMS and KFintech use the same snake_case field names and values (for example eligibility: "Y", net_eligible_balance, category: "WDL_AUTH_PARTIAL", wdl_ack_id), and this contract uses them unchanged. The values are common to every CRA: a flag is always Y or N, an amount is always a JSON number, req_id is always a string and withdrawal_type is always WDL_AUTH_PARTIAL or WDL_AUTH_FULL, whichever CRA answered. Every value the HBA sends (category, patient_type) is likewise one set, translated by NPS Central for each CRA. The few fields NPS Central adds (id, status, checked_at, as_of, otp_expires_at, consent_expires_at, failure_reason) follow the same case. The CRA's OTP token and its raw access_key are never returned.

APIHosted byMethod and path (relative to the base URL)
Token (OAuth 2.0)NPS CentralPOST /api/v1/auth/partner-token
EligibilityNPS CentralPOST /api/v1/swasthya/eligibility
Balance – Consent RequestNPS CentralPOST /api/v1/swasthya/balance-consents
Balance – Consent VerificationNPS CentralPOST /api/v1/swasthya/balance-consents/{consentRequestId}/verify
Balance – ReadNPS CentralPOST /api/v1/swasthya/balance
Withdrawal – InitiationNPS CentralPOST /api/v1/swasthya/withdrawals
Withdrawal – SubmissionNPS CentralPOST /api/v1/swasthya/withdrawals/{withdrawalId}/submit
Withdrawal – StatusNPS CentralGET /api/v1/swasthya/withdrawals/{withdrawalId}

Recordkeepers served in this version: CAMS and KFintech. A PRAN held by Protean is answered with 501 SWASTHYA_WITHDRAWAL_UNSUPPORTED. Eligibility (Part I) and Withdrawal (Part III) for Protean will be served through the same APIs once NPS Central enables them, with no HBA change. Protean offers no balance confirmation (it has no AUTH OTP), so Balance (Part II) will go on answering 501 for a Protean PRAN.

The CRAs authorise each kind of call differently, and this contract follows them exactly. Every OTP in this document is the CRA's own. It is sent by the CRA to the mobile number registered against the PRAN, and checked by the CRA. NPS Central sends no OTP of its own, and the HBA does not need to log the subscriber in first.

CallSubscriber consentScopeLasts
EligibilityNoneswasthya:eligibilityOne answer per call
BalanceThe CRA's AUTH OTP, onceswasthya:balanceAn access_key, reusable for balance reads until it expires: as long as the CRA allows, up to 30 days
WithdrawalThe CRA's withdrawal OTP, every time, raised for that amountswasthya:withdrawOne withdrawal. The OTP is single use and valid 15 minutes

1.3Data held by NPS Central

  • Balance consent request: the CRA's OTP token, held only for the CRA's 15-minute OTP window, then deleted.
  • access_key: nothing. The CRA's access_key is sealed into the access_key value that the HBA holds, and NPS Central opens it only when the HBA presents it. NPS Central does not store it.
  • Withdrawal: a working record (id, PRAN, amount, category, patient type, status, the CRA's req_id, withdrawal_type and wdl_ack_id), readable for 60 minutes after initiation. The CRA's OTP token is deleted the moment the withdrawal is submitted.
  • Audit: a record of every eligibility check, consent and withdrawal status change, under NPS Central's retention policy. It never contains an OTP, a CRA token or an access_key.
  • NPS Central does not keep the subscriber's corpus, balance or payout details. Every balance is a live read from the CRA.

2Common Journeys

2.1Eligibility (no OTP)

StepActorActionAPI
1HBAObtains a partner access token (cached for up to 15 minutes).Token
2HBASends the PRAN, and the claim amount if known.Eligibility
3NPS CentralFinds the CRA holding the PRAN, runs the CRA's enrolment check and its eligibility check, and returns the answer.–

2.2Balance (AUTH OTP once, then reads for the consent's life)

StepActorActionAPI
1HBARequests a balance consent for the PRAN.Balance – Consent Request
2NPS CentralRuns the CRA's enrolment check and asks the CRA to send its AUTH OTP.–
3SubscriberReceives the OTP from the CRA on their registered mobile and gives it to the HBA.–
4HBASubmits the OTP. Receives the access_key and the first balance.Balance – Consent Verification
5HBAWhenever it needs the balance again, until the access_key expires, presents it. No OTP.Balance – Read

2.3Withdrawal (an OTP every time)

StepActorActionAPI
1HBAInitiates a withdrawal with the PRAN, the category (WDL_AUTH_PARTIAL / WDL_AUTH_FULL) and the claim amount.Withdrawal – Initiation
2NPS CentralRuns the CRA's enrolment and eligibility checks and asks the CRA to send the withdrawal OTP for that category and amount.–
3SubscriberReceives the withdrawal OTP from the CRA (valid 15 minutes) and gives it to the HBA.–
4HBASubmits the OTP and the patient type (OPD / IPD). NPS Central answers at once with SUBMITTING and calls the CRA in the background.Withdrawal – Submission
5HBAPolls every 3–5 seconds until the status is COMPLETED, FAILED or UNCONFIRMED.Withdrawal – Status
6HBAOn COMPLETED, records the CRA's acknowledgement (wdl_ack_id) for reconciliation. The payout follows at the CRA.–

2.4Sequence

#HBA callsNPS Central doesCRA does
1TokenIssues a 15-minute access token–
2EligibilityEnrolment and eligibility checks at the CRAAnswers; no OTP
3Balance – Consent RequestEnrolment check; asks for the AUTH OTPSends the OTP to the subscriber
4Balance – Consent Verification (OTP)Validates the OTP; first balance read; returns the access_key sealedIssues the access_key; answers the balance
5Balance – Read (any number of times until expiry)Balance read with the CRA's access_keyAnswers the balance
6Withdrawal – InitiationEnrolment and eligibility; asks for the withdrawal OTPSends the OTP to the subscriber
7Withdrawal – Submission (OTP)Answers 202 SUBMITTING; submits the withdrawalAcknowledges the withdrawal
8Withdrawal – Status (poll)Returns COMPLETED (wdl_ack_id), FAILED or UNCONFIRMED–

3Common API Standards

AreaStandard Requirement
ProtocolHTTPS REST APIs. TLS 1.2 minimum, TLS 1.3 preferred. Plain HTTP is not permitted in any environment.
PayloadJSON (UTF-8). Field names are snake_case, as the CRAs name them (1.1), and case-sensitive.
VersioningMajor version in the path: /api/v1/… Adding an optional response field is not a breaking change; HBAs must ignore response fields they do not recognise.
AuthenticationOAuth 2.0 client credentials. The HBA's access token is sent as a bearer token on every call (3.2).
Payload encryptionPer the HBA's registration: off, optional or required. When used, the NPSC envelope (3.3).
Request IDEvery response carries an X-Request-Id header. The HBA may send its own; otherwise NPS Central generates one (3.4).
Duplicate protectionA withdrawal OTP can be submitted exactly once. A withdrawal is never retried on the same id (3.7).
Asynchronous outcomeWithdrawal submission answers 202 at once; the outcome is read with the Status API (Part III).
Error handlingErrors are RFC 9457 problem documents (application/problem+json) with a stable category and code (3.8, Annexure A).
AuditNPS Central audits every call in this document (1.3, 3.9).

3.1Environments and network

EnvironmentBase URL
UAThttps://uat.npscentral.in/nivruti
Productionhttps://npscentral.in/nivruti
  • All paths in this document are appended to the base URL unchanged. For example, Eligibility in UAT is POST https://uat.npscentral.in/nivruti/api/v1/swasthya/eligibility.
  • Personal data (PRAN, OTP, amount) is sent only in request bodies, never in URLs or query strings. This is why eligibility and balance use POST. The ids in paths (swc_…, wdl_…) are opaque references.

3.2Authentication

The HBA authenticates with the OAuth 2.0 client credentials grant (RFC 6749 §4.4). Its client credential goes only to the token endpoint. Every API call carries the short-lived access token the endpoint returns.

3.2.1 What is agreed at onboarding
ItemHBA value
Client authenticationclient_secret_basic or client_secret_post. NPS Central generates a 256-bit client secret and hands it over once, over an agreed secure channel. The HBA never chooses it. The method is fixed by the registration; the other is refused, even with valid credentials.
ScopesAny of swasthya:eligibility, swasthya:balance, swasthya:withdraw (1.2).
Payload encryptionWhen used (3.3), a 256-bit encryption key with its keyId is issued alongside the client secret.

The HBA receives its clientId, its client secret, and its encryption key if encryption is used.

POST/api/v1/auth/partner-tokenToken request
Content type
application/x-www-form-urlencoded
client_secret_basic
Authorization: Basic base64(clientId:clientSecret). Body: grant_type=client_credentials&scope=swasthya:eligibility swasthya:balance swasthya:withdraw
client_secret_post
Body: grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&scope=…
scope
Optional. The token carries the scopes asked for, intersected with the scopes registered. Omitted: every registered scope.
Secrets
Never in a query string. A request carrying one there is refused, because it would land in access logs.
3.2.3 Token response — 200 OKJSON
{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900,
  "scope": "swasthya:eligibility swasthya:balance swasthya:withdraw" }
  • Lifetime: 15 minutes. There is no refresh token: request a new token shortly before expiry. Cache and reuse it; do not request one per call. The token endpoint is rate-limited per client id.
  • API calls: Authorization: Bearer <access_token>.
  • Live registration: the token proves who the HBA is, but whether it may still act is read from its registration on every call. If NPS Central suspends the HBA or narrows its scopes, live tokens stop working on their next request.
3.2.4 Token errors (OAuth form, not a problem document)
HTTPerrorMeaning
400unsupported_grant_type / invalid_requestThe request is malformed.
400invalid_scopeOnly scopes the HBA does not hold were asked for.
401invalid_clientAny authentication failure: unknown client, suspended, wrong method, wrong secret. Deliberately one answer.
429slow_downToo many attempts. Back off.

3.3Payload encryption

TLS protects every call. On top of it, the HBA's registration sets a message encryption policy. No request header changes it.

PolicyRequestsResponses
offJSONJSON
optionalJSON or NPSC envelopeIn kind: an enveloped request is answered in the envelope
requiredNPSC envelope only. A plain JSON body is refused: 400 ENCRYPTION_REQUIREDEvery response is in the envelope, error bodies included

Encryption uses the NPSC envelope: a JSON envelope sealed with AES-256-GCM under the encryption key issued to the HBA, sent with Content-Type: application/vnd.npsc.envelope+json. No JOSE library is needed. A request with a body is answered in the envelope when it was sent in one. A request with no body (Withdrawal – Status) is answered in the envelope when its Accept header is application/vnd.npsc.envelope+json; under the required policy, send this header on that call. Not encrypted: the token endpoint (plain OAuth) and the X-Request-Id response header.

3.3.1 The NPSC envelopeJSON
{
  "version": "NPSC1",
  "keyId": "jwe_3f9c…",
  "randomNo": "00112233445566778899aabbccddeeff",
  "timestamp": 1760261400,
  "encData": "oKGio6SlpqeoqaqrW6Zcn0RWG8Li…"
}
Envelope fields
  • version
    StringRequired

    Rules: Only NPSC1

    Envelope version.

  • keyId
    StringRequired

    Rules: A live keyId issued to the HBA

    Key the message is sealed under. NPS Central answers under the same key while it is live.

  • randomNo
    String 32Required

    Rules: 16 random bytes from a secure generator, lowercase hex. New for every message; a reused value is refused

    Makes the message's own key.

  • timestamp
    IntegerRequired

    Rules: Epoch seconds; within 5 minutes of NPS Central's clock

    When the message was sealed.

  • encData
    String (base64)Required

    Rules: base64( iv (12 bytes) + ciphertext + tag (16 bytes) )

    The sealed JSON body.

Sealing a request:

  1. K = HKDF-SHA256( ikm = the issued encryption key (32 bytes), salt = randomNo as bytes, info = "NPSC1" + 0x00 + SHA-256(access token) ), 32 bytes. The access token is the bearer sent on this call, exactly as sent.
  2. AAD = "NPSC1|REQ|" + METHOD + "|" + path + "|" + keyId + "|" + randomNo + "|" + timestamp, UTF-8. path is the path from /api onwards, without the /nivruti prefix of the base URL and without the query string, e.g. /api/v1/swasthya/withdrawals.
  3. A random 12-byte IV; AES-256-GCM(K, iv, AAD) over the JSON body (UTF-8), 16-byte tag.
  4. encData = base64(iv + ciphertext + tag).

Opening a response is the same with RES in place of REQ, the response's own randomNo, timestamp and keyId, and the request's method and path. Because the key is bound to the access token and the AAD to the method and path, a body sealed for one call cannot be replayed to another route or under another token. Refusals: a bad shape, a stale timestamp, a reused randomNo, a keyId that is not live, or a body that does not open are all 400 ENVELOPE_INVALID. A test vector and Node and Java reference code are in Annexure C. Check the implementation against the test vector before the first UAT call.

3.4Common HTTP headers

Common HTTP headers
  • Authorization
    StringRequired

    Rules: Bearer <access_token>, on every API except the token endpoint.

  • Content-Type
    StringConditional

    Rules: On every POST with a body: application/json, or application/vnd.npsc.envelope+json under encryption (3.3).

  • Accept
    StringConditional

    Rules: application/vnd.npsc.envelope+json on Withdrawal – Status under encryption (3.3).

  • X-Request-Id
    String 1–64Optional

    Rules: The HBA's trace id for this HTTP call; a new UUID v4 on every attempt is recommended. Echoed in the response header and in the requestId of an error body. Generated by NPS Central when absent.

No Idempotency-Key header is needed on these APIs. Duplicate protection is built into the withdrawal flow (3.7).

3.5Data type conventions

TypeFormatExampleRule
Amount (request)Number, or string of digits, at most 2 decimal places1500 or "1500.00"INR. Up to 9 integer digits; a string matches ^[0-9]{1,9}(\.[0-9]{1,2})?$. No currency symbol, commas or negatives. A withdrawal amount must be greater than zero.
Amount (response)Number18450.5INR. CRA balances (net_eligible_balance, total_corpus) are always a JSON number, at the precision the CRA gave. The withdrawal amount NPS Central echoes is a string with two decimals.
PRANString 12 digits900090067007^[0-9]{12}$
OTPString 4–8 digits"482913"^[0-9]{4,8}$. Sent as a string so leading zeros are kept.
TimestampString ISO 8601, UTC2026-10-13T09:30:00.000ZUTC with Z suffix.
FlagString Y / N"Y"The CRAs' flag values, always Y or N whichever CRA answered. A flag the CRA did not send is omitted.
EnumString, UPPER_SNAKE_CASEWDL_AUTH_PARTIALCase-sensitive. Values listed per field and in Annexure B.
CodeString"1"Codes are strings, never numbers.
IdentifierString with a prefixwdl_2b7e…Opaque. Store and echo it unchanged; do not parse it.

Absent and empty are different: an optional response field with no value is omitted, never sent as an empty string. Send only the request fields defined for each API; a field not defined may be refused with 400 REQUEST_INVALID.

3.6Identifiers and correlation

IdentifierGenerated byPurpose and life
clientIdNPS CentralThe HBA's identity. Issued at onboarding.
access_tokenNPS CentralBearer credential for API calls. 15 minutes. Never logged.
consentRequestId (swc_…)NPS CentralOne per Balance – Consent Request. Key for Consent Verification. Lives for the CRA's 15-minute OTP window, visible only to the HBA that raised it.
access_key (swc1.…)NPS Central, sealing the CRA'sThe CRA's access_key, sealed so that only NPS Central can open it. Valid only for the HBA that obtained it and that PRAN, until consent_expires_at. A credential: stored encrypted, never logged, never parsed.
withdrawalId (wdl_…)NPS CentralOne per Withdrawal – Initiation. Key for Submission and Status. Readable for 60 minutes, by the HBA that raised it only.
req_idCRAThe CRA's reference for the eligibility, balance or withdrawal request, always as a string. Quote it to the CRA or NPS Central support.
wdl_ack_idCRAThe CRA's withdrawal acknowledgement number. Present only on COMPLETED. The HBA's reference for reconciliation with the CRA.
X-Request-Id / requestIdHBA or NPS CentralOne per HTTP call. Tracing and support only; never used for business correlation.

Ownership. Everything an HBA creates belongs to its clientId. Another HBA presenting the id receives the same 404 as for an id that does not exist.

3.7Duplicate protection and retries

  • Eligibility and Balance – Read are reads and safe to repeat (eligibility is rate-limited per PRAN, 3.10).
  • Balance – Consent Request is not idempotent: each call asks the CRA to send a new OTP. Do not repeat it on a timeout without checking with the subscriber.
  • Balance – Consent Verification may be retried after a wrong OTP within the 15-minute window. Two verifications of one request at the same time are refused with 409 SWASTHYA_CONSENT_VERIFY_IN_PROGRESS.
  • Withdrawal – Initiation is not idempotent: each call creates a new withdrawalId and asks the CRA for a new OTP.
  • Withdrawal – Submission is accepted once per withdrawalId. Any later submission for the same id is refused with 409 SWASTHYA_WITHDRAWAL_ALREADY_SUBMITTED, whatever the outcome of the first.
  • A FAILED withdrawal is never retried on the same id. To try again, start a new withdrawal; the subscriber receives a new OTP.
  • An UNCONFIRMED withdrawal must not be re-initiated automatically. The CRA may have accepted it, and a new withdrawal could pay the claim twice. It is resolved through NPS Central support, quoting the withdrawalId.
  • Withdrawal – Status is a read and safe to repeat.
  • Under encryption, a retry re-sealed with a fresh randomNo is still the same request.

3.8Response structure and errors

A successful response body carries the API-specific fields described in each Part. Every error response (except the token endpoint's, 3.2.4) is a problem document with Content-Type: application/problem+json (encrypted under the required policy). HBAs branch on category and code, never on detail text.

Problem document fields
  • type
    String (URI)Required

    Problem type, derived from category.

  • title
    StringRequired

    Short description of the category.

  • status
    IntegerRequired

    HTTP status code; equals the HTTP status.

  • category
    EnumRequired

    Class of error; decides the client's handling (Annexure B.5).

  • code
    String 1–60Required

    Stable machine code within the category (Annexure A).

  • detail
    StringOptional

    Readable explanation. Where the CRA gave a reason, its sentence with the CRA's name replaced by "the recordkeeper". Never names a CRA.

  • instance
    StringOptional

    The request path.

  • requestId
    StringOptional

    Same value as the X-Request-Id header. Per-call trace id for support.

  • diagnosticRef
    StringOptional

    Opaque handle NPS Central support uses to find the full CRA exchange. Quote it in a support ticket.

  • retryable
    BooleanRequired

    Whether repeating the same request unchanged may succeed. true for RATE_LIMITED, CRA_UNAVAILABLE, CRA_TIMEOUT, TRANSIENT.

  • errors
    ArrayConditional

    One entry per invalid field, present on field validation failures.

  • errors[].field
    StringRequired

    Offending field, e.g. amount.

  • errors[].code
    StringRequired

    Field error code, e.g. FIELD_INVALID.

  • errors[].message
    StringRequired

    Readable field error.

HTTP status codes: 200/201/202 for a processed call; 400 for a body that could not be read or failed field validation; 401 for authentication (including a route outside the token's scopes, and an invalid or expired access_key); 404 for an id that does not exist or is not the HBA's (deliberately one answer); 409 for a state conflict; 422 for a request that was read and refused, including CRA refusals; 429 for rate limiting; 501 for an unsupported recordkeeper; 503 when the CRA could not be reached. A withdrawal the CRA refuses after submission is not an HTTP error: Withdrawal – Status answers 200 with status: FAILED and a failure_reason (Part III).

3.9Audit, logging and data protection

  • Personal data is processed only to carry out the NPS Swasthya service requested by the subscriber, under the Digital Personal Data Protection Act, 2023 and applicable PFRDA and IRDAI regulations.
  • NPS Central never logs or stores an OTP, a CRA token or the CRA's balance credential. PRANs are masked in operational logs.
  • Eligibility discloses whether a PRAN holds NPS Swasthya, so every eligibility call is audited against the HBA and rate-limited per PRAN.
  • The HBA:
    • never logs the access token, the client secret, the encryption key, any OTP or the access_key;
    • stores the access_key as a credential: encrypted, against the subscriber, and deleted when it expires or the subscriber withdraws consent;
    • keeps its own record of each withdrawalId, wdl_ack_id, amount and final status for reconciliation;
    • obtains and records the subscriber's consent to each balance read and withdrawal under its agreement with the subscriber and the insurer.
  • Retention of NPS Central's audit trail: as per applicable regulation.

3.10Service levels and timings

ItemValueNotes
Access token lifetime15 minutesexpires_in = 900. No refresh token.
CRA OTP validity (AUTH and withdrawal)15 minutes from the requestotp_expires_at in the response.
access_key lifetimeAs long as the CRA allows, up to 30 daysconsent_expires_at in the verification response. Never longer than the CRA issued; may end early if the CRA ends it.
Withdrawal record read-back60 minutes from initiationAfter that Withdrawal – Status answers 404.
CRA withdrawal callTypically 20–40 secondsWhy submission is asynchronous.
No answer from the CRA after submission3 minutesReported as UNCONFIRMED (OUTCOME_UNKNOWN).
Recommended polling interval3–5 secondsStop at COMPLETED, FAILED or UNCONFIRMED.
Eligibility rate limit20 calls per hour per HBA per PRAN429 RATE_LIMITED beyond it.

Planned maintenance on either side requires 48 hours' notice. Incidents are reported per the escalation matrix exchanged at onboarding.

Part I – Eligibility API

4NPS Swasthya – Eligibility API Standard

The Eligibility API lets the HBA learn, without an OTP, whether a PRAN holds NPS Swasthya and whether the CRA considers it eligible for a withdrawal, optionally for a given claim amount.

Responsibility
PartyResponsibility
HBASends the PRAN of a subscriber it services, and the claim amount if known.
NPS CentralFinds the CRA holding the PRAN, runs the CRA's enrolment and eligibility checks, returns the answer, audits the call and applies the rate limit.
CRAAnswers enrolment and eligibility.
POST/api/v1/swasthya/eligibilityEligibility
Direction
HBA → NPS Central → CRA
Scope
swasthya:eligibility
Subscriber consent
None
Response
Synchronous, HTTP 200

What NPS Central does: applies the per-PRAN rate limit; finds the CRA holding the PRAN (after an inter-CRA shift the previous CRA still answers, the CRA where the account is active is used); runs the CRA's enrolment check, refusing (422) unless the PRAN exists, holds an NPS Swasthya scheme, has a balance and is not an NPS Vatsalya PRAN; runs the eligibility check and returns the CRA's eligibility fields. Balances are not returned here: they need the subscriber's consent (Part II).

4.5Request parameters

Request parameters
  • pran
    String 12Required

    Rules: ^[0-9]{12}$

    Subscriber's PRAN.

  • estimated_value
    AmountOptional

    Rules: At most 2 decimals

    The claim amount, if known.

4.6Response parameters

Response parameters
  • req_id
    StringOptional

    Rules: Always a string

    The CRA's reference for the check.

  • pran
    String 12Required

    Echo of the PRAN.

  • eligibility
    FlagConditional

    Rules: Y / N

    Y: eligible. N: not eligible; reason in message. Absent: this CRA answers eligibility only under the subscriber's consent; take an access_key (Part II), whose answer carries it.

  • message
    StringOptional

    Rules: Never names the CRA

    The CRA's own words on eligibility.

  • eligibility_as_per_corpus
    FlagOptional

    Rules: Y / N

    Whether the corpus allows a full withdrawal (category WDL_AUTH_FULL, Part III).

  • message_as_per_full_corpus
    StringOptional

    Rules: Never names the CRA

    The CRA's own words on the full-corpus check.

  • checked_at
    TimestampRequired

    When the check was made.

4.7Error parameters

HTTPcodeWhen
400REQUEST_INVALIDpran or estimated_value malformed (errors[] names the field)
401AUTH_FAILEDBad or expired token, suspended registration, or scope swasthya:eligibility not held
422SWASTHYA_PRAN_NOT_FOUNDNo recordkeeper holds this PRAN
422SWASTHYA_NO_SWASTHYA_SCHEMEThe PRAN has no NPS Swasthya scheme
422SWASTHYA_NO_BALANCENothing is available to withdraw
422SWASTHYA_VATSALYA_NOT_ALLOWEDNPS Vatsalya PRANs cannot withdraw
422SWASTHYA_NOT_ELIGIBLEThe CRA refused the eligibility check
429RATE_LIMITEDToo many eligibility checks for this PRAN by this HBA
501SWASTHYA_WITHDRAWAL_UNSUPPORTEDThe PRAN's recordkeeper does not offer NPS Swasthya through NPS Central yet
503SWASTHYA_CRA_UNAVAILABLEThe CRA did not answer, or no CRA has the account active while one did not answer. Retry shortly

4.8Sample request and response

POST /api/v1/swasthya/eligibilityJSON
{ "pran": "900090067007", "estimated_value": 1500 }

// 200 OK
{
  "req_id": "9541",
  "pran": "900090067007",
  "eligibility": "Y",
  "message": "PRAN is eligible for withdrawal",
  "eligibility_as_per_corpus": "N",
  "message_as_per_full_corpus": "Full withdrawal is not allowed as per corpus",
  "checked_at": "2026-10-13T09:30:00.000Z"
}
Part II – Balance API

5NPS Swasthya – Balance API Standard

The Balance API lets the HBA show the subscriber the balance eligible for withdrawal. The subscriber consents once, with the CRA's AUTH OTP. The HBA then holds the CRA's access_key, sealed by NPS Central, and reads the balance with it, without a new OTP, until it expires.

API definition
ParameterConsent RequestConsent VerificationBalance Read
MethodPOSTPOSTPOST
Endpoint/api/v1/swasthya/balance-consents/api/v1/swasthya/balance-consents/{consentRequestId}/verify/api/v1/swasthya/balance
Scopeswasthya:balanceswasthya:balanceswasthya:balance
Subscriber consentStarts it: the CRA sends the AUTH OTPThe AUTH OTPThe access_key
ResponseHTTP 201HTTP 200HTTP 200

NPS Central finds the CRA holding the PRAN (the CRA where the account is active), runs the CRA's enrolment check, and asks the CRA to send its AUTH OTP to the mobile registered against the PRAN.

Request parameters
  • pran
    String 12Required

    Rules: ^[0-9]{12}$

    Subscriber's PRAN.

Response parameters
  • id
    StringRequired

    Rules: Prefix swc_

    consentRequestId. Key for Consent Verification. Visible only to this HBA.

  • status
    EnumRequired

    Rules: PENDING_OTP

    The OTP has been sent.

  • category
    EnumRequired

    Rules: AUTH

    The CRA's OTP category for a balance consent.

  • otp_expires_at
    TimestampRequired

    Rules: 15 minutes from now

    The CRA's OTP window.

  • message
    StringRequired

    Rules: Never names the CRA

    The CRA's own words.

NPS Central validates the OTP at the CRA, receives the CRA's access_key, reads the first balance with it, and returns the access_key sealed.

Request parameters
  • consentRequestId (path)
    StringRequired

    Rules: Prefix swc_; from Consent Request

    The consent request.

  • otp_value
    String 4–8Required

    Rules: ^[0-9]{4,8}$

    The AUTH OTP the subscriber received.

  • estimated_value
    AmountOptional

    Rules: At most 2 decimals

    The claim amount, if known, for the first balance read.

Response parameters
  • id
    StringRequired

    Echo of consentRequestId.

  • status
    EnumRequired

    Rules: VERIFIED

    The OTP was accepted.

  • access_key
    StringRequired

    Rules: Prefix swc1.; opaque

    The CRA's access_key, sealed (5.7). Valid only for this HBA and this PRAN.

  • consent_expires_at
    TimestampRequired

    Rules: At most 30 days from now

    When the access_key ends.

  • balance
    ObjectConditional

    The first balance, as in 5.6.b. Absent when the first read could not be made; then call Balance – Read.

  • message
    StringOptional

    Says why balance is absent, when it is.

A wrong OTP is 422 SWASTHYA_OTP_INVALID; the subscriber may retype it within the window. A spent or expired CRA token is 409 SWASTHYA_OTP_EXPIRED, and a request whose 15 minutes have passed is 404 SWASTHYA_CONSENT_NOT_FOUND. Either way, start again with a new Consent Request.

5.6Balance – Read

NPS Central opens the access_key, checks that it was issued to this HBA for this PRAN, and reads the balance from the CRA with the credential inside. Each call is a live read; nothing is cached.

Request parameters
  • pran
    String 12Required

    Rules: ^[0-9]{12}$; the PRAN the consent was issued for

    Subscriber's PRAN.

  • access_key
    StringRequired

    Rules: Exactly as received; ^swc1\.[A-Za-z0-9._-]{1,32}\.[A-Za-z0-9_-]+$

    The access_key from Consent Verification.

  • estimated_value
    AmountOptional

    Rules: At most 2 decimals

    The claim amount, if known.

Response parameters (the balance object)
  • req_id
    StringOptional

    Rules: Always a string

    The CRA's reference for the read.

  • pran
    String 12Required

    Echo of the PRAN.

  • eligibility
    FlagOptional

    Rules: Y / N

    Whether the PRAN is eligible for withdrawal.

  • message
    StringOptional

    Rules: Never names the CRA

    The CRA's own words on eligibility.

  • net_eligible_balance
    NumberOptional

    Rules: INR, at the CRA's precision

    What may be withdrawn now.

  • total_corpus
    NumberOptional

    Rules: INR, at the CRA's precision

    Total NPS Swasthya corpus.

  • eligibility_as_per_corpus
    FlagOptional

    Rules: Y / N

    Whether the corpus allows a full withdrawal.

  • message_as_per_full_corpus
    StringOptional

    Rules: Never names the CRA

    The CRA's own words on the full-corpus check.

  • as_of
    TimestampRequired

    When the balance was read.

5.7The access_key

  • The CRA's access_key, sealed. It has the CRA's field name but not the CRA's value: only NPS Central can open it, and the raw key never leaves NPS Central. Do not parse it.
  • Bound to the HBA and the PRAN. Presented by another HBA, or with another PRAN, it is 401 SWASTHYA_CONSENT_INVALID.
  • Lasts as long as the CRA allows, up to 30 days, never longer. If the CRA ends it early, the read answers 401 SWASTHYA_CONSENT_EXPIRED: take a new AUTH OTP with a new Consent Request.
  • Read-only. It never authorises a withdrawal (Part III).
  • Store it as a credential: encrypted, against the subscriber, deleted when it expires or the subscriber withdraws consent.
  • Suspending the HBA's registration ends every one of its consents at once.

5.8Error parameters

HTTPcodeWhen
400REQUEST_INVALIDA field is malformed (errors[] names it)
401AUTH_FAILEDBad or expired token, suspended registration, or scope swasthya:balance not held
401SWASTHYA_CONSENT_INVALIDBalance – Read: the access_key is malformed, not this HBA's, or for another PRAN
401SWASTHYA_CONSENT_EXPIREDBalance – Read: the access_key has expired or the CRA no longer accepts it. Take a new AUTH OTP
404SWASTHYA_CONSENT_NOT_FOUNDVerification: the request is not this HBA's, does not exist, or its 15 minutes have passed
409SWASTHYA_CONSENT_VERIFY_IN_PROGRESSVerification: another verification of the same request is running
409SWASTHYA_OTP_EXPIREDVerification: the CRA's OTP token is spent or expired. Start again
422SWASTHYA_OTP_INVALIDVerification: wrong OTP. May be retried within the window
422SWASTHYA_PRAN_NOT_FOUND / …Consent Request: the CRA's enrolment check refused (as Part I)
422SWASTHYA_OTP_REFUSEDConsent Request: the CRA did not send an OTP
422SWASTHYA_NOT_ELIGIBLEThe CRA refused the eligibility read
501SWASTHYA_WITHDRAWAL_UNSUPPORTEDThe PRAN is held by Protean, which offers no balance confirmation. Permanent for a Protean PRAN
503SWASTHYA_CRA_UNAVAILABLEThe CRA did not answer. Retry shortly

5.9Sample requests and responses

Consent Request and VerificationJSON
POST /api/v1/swasthya/balance-consents
{ "pran": "900090067007" }

// 201 Created
{ "id": "swc_6f1c0d2e9a7b4c55", "status": "PENDING_OTP", "category": "AUTH",
  "otp_expires_at": "2026-10-13T09:45:00.000Z",
  "message": "OTP Triggered successfully" }

POST /api/v1/swasthya/balance-consents/swc_6f1c0d2e9a7b4c55/verify
{ "otp_value": "123456" }

// 200 OK
{
  "id": "swc_6f1c0d2e9a7b4c55",
  "status": "VERIFIED",
  "access_key": "swc1.k-2026-10.Qm9…",
  "consent_expires_at": "2026-11-12T09:31:00.000Z",
  "balance": {
    "req_id": "9542", "pran": "900090067007", "eligibility": "Y",
    "message": "PRAN is eligible for withdrawal",
    "net_eligible_balance": 18450, "total_corpus": 24600,
    "eligibility_as_per_corpus": "N",
    "message_as_per_full_corpus": "Full withdrawal is not allowed as per corpus",
    "as_of": "2026-10-13T09:31:00.000Z"
  }
}
Balance – ReadJSON
POST /api/v1/swasthya/balance
{ "pran": "900090067007", "access_key": "swc1.k-2026-10.Qm9…", "estimated_value": 1500 }

// 200 OK
{ "req_id": "9560", "pran": "900090067007", "eligibility": "Y",
  "message": "PRAN is eligible for withdrawal",
  "net_eligible_balance": 18450, "total_corpus": 24600,
  "eligibility_as_per_corpus": "N",
  "message_as_per_full_corpus": "Full withdrawal is not allowed as per corpus",
  "as_of": "2026-10-20T11:02:00.000Z" }
Part III – Withdrawal API

6NPS Swasthya – Withdrawal API Standard

The Withdrawal API lets the HBA raise an NPS Swasthya withdrawal for a claim, authorised by the subscriber with the CRA's withdrawal OTP for that category and amount, and follow it to the CRA's acknowledgement.

API definition
ParameterInitiationSubmissionStatus
MethodPOSTPOSTGET
Endpoint/api/v1/swasthya/withdrawals/api/v1/swasthya/withdrawals/{withdrawalId}/submit/api/v1/swasthya/withdrawals/{withdrawalId}
Scopeswasthya:withdrawswasthya:withdrawswasthya:withdraw
Subscriber consentStarts it: the CRA sends the withdrawal OTPThe withdrawal OTP–
ResponseSynchronous, HTTP 201Asynchronous, HTTP 202HTTP 200

6.4Withdrawal – Initiation

What NPS Central does: finds the CRA holding the PRAN; runs the enrolment check (refuses unless the PRAN exists, holds an NPS Swasthya scheme, has a balance and is not an NPS Vatsalya PRAN); runs the eligibility check for the amount (refuses only when the CRA says eligibility: N; if the check cannot be made, continues, since the CRA checks again); asks the CRA to send the withdrawal OTP for the category and amount the HBA sent. NPS Central does not derive or change the category; the CRA decides whether it allows it. The amount is fixed at this step.

Request parameters
  • pran
    String 12Required

    Rules: ^[0-9]{12}$

    Subscriber's PRAN.

  • category
    EnumRequired

    Rules: WDL_AUTH_PARTIAL / WDL_AUTH_FULL

    The CRA's withdrawal OTP category. For an OPD claim the CRAs expect WDL_AUTH_PARTIAL; WDL_AUTH_FULL only where eligibility_as_per_corpus is Y.

  • amount
    AmountRequired

    Rules: > 0; at most 2 decimals; up to 9 integer digits

    The claim amount. Fixed into the OTP.

Response parameters
  • id
    StringRequired

    Rules: Prefix wdl_

    withdrawalId. Key for Submission and Status. Visible only to this HBA.

  • status
    EnumRequired

    Rules: PENDING_OTP

    The OTP has been sent.

  • pran
    String 12Required

    Echo of the PRAN.

  • category
    EnumRequired

    Rules: WDL_AUTH_PARTIAL / WDL_AUTH_FULL

    The category the OTP was raised for.

  • amount
    StringRequired

    Rules: Two decimals

    The amount fixed into the OTP.

  • otp_expires_at
    TimestampRequired

    Rules: 15 minutes from now

    Submit before this.

  • message
    StringOptional

    Rules: Never names the CRA

    The CRA's own words.

6.5Withdrawal – Submission

Submits the subscriber's withdrawal OTP with the patient type, as the CRA's withdrawal request takes them. The CRA can take 20–40 seconds, so NPS Central answers at once with 202 SUBMITTING and calls the CRA in the background. The OTP is single use: the first submission claims the withdrawal (3.7).

Request parameters
  • withdrawalId (path)
    StringRequired

    Rules: Prefix wdl_; from Initiation

    The withdrawal.

  • patient_type
    EnumRequired

    Rules: OPD / IPD

    Out-patient or in-patient claim.

  • otp_value
    String 4–8Required

    Rules: ^[0-9]{4,8}$

    The withdrawal OTP the subscriber received.

Response: HTTP 202 with the withdrawal object (6.6.b), status: SUBMITTING.

6.6Withdrawal – Status

Poll every 3–5 seconds after submission until the status is terminal. Readable for 60 minutes after initiation. The request takes only withdrawalId (path), prefix wdl_.

Response parameters (the withdrawal object)
  • id
    StringRequired

    Rules: Prefix wdl_

    withdrawalId.

  • status
    EnumRequired

    Rules: Annexure B.3

    Current status.

  • pran
    String 12Required

    Echo of the PRAN.

  • category
    EnumRequired

    Rules: WDL_AUTH_PARTIAL / WDL_AUTH_FULL

    The category the OTP was raised for.

  • amount
    StringRequired

    Rules: Two decimals

    The amount.

  • patient_type
    EnumConditional

    Rules: OPD / IPD; once submitted

    As sent at Submission.

  • otp_expires_at
    TimestampConditional

    Rules: While PENDING_OTP

    OTP deadline.

  • req_id
    StringConditional

    Rules: Once the CRA answered

    The CRA's reference for the withdrawal request.

  • withdrawal_type
    EnumConditional

    Rules: WDL_AUTH_PARTIAL / WDL_AUTH_FULL; always on COMPLETED

    The withdrawal type the CRA recorded; where it gave none, the category the OTP was raised for.

  • wdl_ack_id
    StringConditional

    Rules: On COMPLETED

    The CRA's withdrawal acknowledgement. Reconcile against it.

  • failure_reason
    EnumConditional

    Rules: On FAILED or UNCONFIRMED; Annexure B.3

    Why the withdrawal did not complete.

  • message
    StringOptional

    Rules: Never names the CRA

    The CRA's own words where it gave them; otherwise NPS Central's.

Status values
statusMeaningWhat the HBA does
PENDING_OTPThe OTP has been sent; nothing submitted yetSubmit the OTP before otp_expires_at
SUBMITTINGSubmitted; the CRA has not answered yetKeep polling
COMPLETEDThe CRA acknowledged the withdrawal; wdl_ack_id is its referenceRecord wdl_ack_id and settle against it. The payout follows at the CRA
FAILEDThe CRA refused it, or it could not be sent; see failure_reason and messageIf appropriate, start a new withdrawal (new OTP)
UNCONFIRMEDNo answer was received within 3 minutes; it may have been processed (failure_reason OUTCOME_UNKNOWN)Do not retry. Raise it with NPS Central support quoting the withdrawalId

6.7Error parameters

HTTPcodeWhen
400REQUEST_INVALIDA field is malformed (errors[] names it)
401AUTH_FAILEDBad or expired token, suspended registration, or scope swasthya:withdraw not held
404SWASTHYA_WITHDRAWAL_NOT_FOUNDNot this HBA's, does not exist, or more than 60 minutes old (one answer)
409SWASTHYA_WITHDRAWAL_ALREADY_SUBMITTEDSubmission: the OTP was already submitted for this withdrawalId. Start a new withdrawal
409SWASTHYA_OTP_EXPIREDSubmission: the 15-minute OTP window has passed. Start a new withdrawal
422SWASTHYA_AMOUNT_INVALIDInitiation: amount is not greater than zero
422SWASTHYA_PRAN_NOT_FOUND / …Initiation: the CRA's enrolment check refused
422SWASTHYA_NOT_ELIGIBLEInitiation: the CRA says the PRAN is not eligible for this amount
422SWASTHYA_OTP_REFUSEDInitiation: the CRA did not send an OTP
501SWASTHYA_WITHDRAWAL_UNSUPPORTEDAs Part I
503SWASTHYA_CRA_UNAVAILABLEInitiation: the CRA did not answer. Retry shortly

6.8Sample requests and responses

Initiation and SubmissionJSON
POST /api/v1/swasthya/withdrawals
{ "pran": "900090067007", "category": "WDL_AUTH_PARTIAL", "amount": 1500 }

// 201 Created
{ "id": "wdl_2b7e5c1a9d3f4e60", "status": "PENDING_OTP", "pran": "900090067007",
  "category": "WDL_AUTH_PARTIAL", "amount": "1500.00",
  "otp_expires_at": "2026-10-13T09:47:00.000Z",
  "message": "OTP Triggered successfully" }

POST /api/v1/swasthya/withdrawals/wdl_2b7e5c1a9d3f4e60/submit
{ "patient_type": "OPD", "otp_value": "654321" }

// 202 Accepted
{ "id": "wdl_2b7e5c1a9d3f4e60", "status": "SUBMITTING", "pran": "900090067007",
  "category": "WDL_AUTH_PARTIAL", "amount": "1500.00", "patient_type": "OPD",
  "message": "Your withdrawal request has been submitted and is being processed." }
Status — COMPLETED and a refusalJSON
GET /api/v1/swasthya/withdrawals/wdl_2b7e5c1a9d3f4e60

// 200 OK
{ "id": "wdl_2b7e5c1a9d3f4e60", "status": "COMPLETED", "pran": "900090067007",
  "category": "WDL_AUTH_PARTIAL", "amount": "1500.00", "patient_type": "OPD",
  "req_id": "9543", "withdrawal_type": "WDL_AUTH_PARTIAL", "wdl_ack_id": "131000006952",
  "message": "Withdrawal initiated successfully in the recordkeeper" }

// 200 OK (a refusal)
{ "id": "wdl_2b7e5c1a9d3f4e60", "status": "FAILED", "pran": "900090067007",
  "category": "WDL_AUTH_PARTIAL", "amount": "1500.00", "patient_type": "OPD",
  "failure_reason": "OTP_INVALID",
  "message": "Invalid OTP" }
Annexures

AError Catalogue

Every code an HBA can receive on the APIs in this document, in addition to the token endpoint's OAuth errors (3.2.4). retryable is true only where repeating the same request unchanged may succeed. The category for each HTTP status is in Annexure B.5.

24 rows

codeHTTPRetryMeaning and HBA action
REQUEST_INVALID400NoA field is malformed. Fix the request.
ENCRYPTION_REQUIRED400NoPlain JSON sent under the required policy. Use the NPSC envelope.
ENVELOPE_INVALID400NoEnvelope malformed, stale, replayed, unknown key, or did not open. Check against the test vector.
AUTH_REQUIRED401NoNo bearer token.
AUTH_FAILED401NoBad, expired or revoked token; suspended registration; or route outside the token's scopes. Get a new token; if it persists, contact NPS Central.
SWASTHYA_CONSENT_INVALID401Noaccess_key malformed, not this HBA's, or for another PRAN.
SWASTHYA_CONSENT_EXPIRED401Noaccess_key expired or ended by the CRA. New Consent Request.
SWASTHYA_CONSENT_NOT_FOUND404NoConsent request unknown, not this HBA's, or past 15 minutes. New Consent Request.
SWASTHYA_WITHDRAWAL_NOT_FOUND404NoWithdrawal unknown, not this HBA's, or past 60 minutes.
SWASTHYA_CONSENT_VERIFY_IN_PROGRESS409NoA verification of the same request is running. Wait for it.
SWASTHYA_OTP_EXPIRED409NoOTP window passed or token spent. Start again.
SWASTHYA_WITHDRAWAL_ALREADY_SUBMITTED409NoOTP already submitted for this withdrawal. Poll its status; start a new withdrawal only if it FAILED.
SWASTHYA_PRAN_NOT_FOUND422NoNo recordkeeper holds this PRAN.
SWASTHYA_NO_SWASTHYA_SCHEME422NoThe PRAN has no NPS Swasthya scheme.
SWASTHYA_NO_BALANCE422NoNothing available to withdraw.
SWASTHYA_VATSALYA_NOT_ALLOWED422NoNPS Vatsalya PRANs cannot withdraw.
SWASTHYA_NOT_ELIGIBLE422NoThe CRA says not eligible; reason in detail.
SWASTHYA_OTP_REFUSED422NoThe CRA did not send an OTP; reason in detail.
SWASTHYA_OTP_INVALID422NoWrong OTP on a consent verification. Retype within the window.
SWASTHYA_AMOUNT_INVALID422NoWithdrawal amount not greater than zero.
RATE_LIMITED429Yes, laterToo many eligibility checks for one PRAN, or too many token requests. Back off.
SWASTHYA_WITHDRAWAL_UNSUPPORTED501NoThe PRAN is held by Protean: not yet served for Eligibility and Withdrawal; never for Balance (1.1).
SWASTHYA_CRA_UNAVAILABLE503YesThe CRA did not answer. Retry shortly (not for Submission, see 3.7).
PARTNER_REGISTRY_UNAVAILABLE503YesTransient fault at NPS Central. Retry.
Annexures

BCode Lists

B.1 category (the CRA's OTP category)
ValueMeaning
AUTHBalance consent (Part II). Set by NPS Central
WDL_AUTH_PARTIALA partial withdrawal from the NPS Swasthya corpus. The CRAs expect it for every OPD claim
WDL_AUTH_FULLA full withdrawal of the NPS Swasthya corpus, where eligibility_as_per_corpus is Y
B.2 patient_type
ValueMeaning
OPDOut-patient claim
IPDIn-patient claim
B.3 Withdrawal status
statusTerminal?
PENDING_OTPNo
SUBMITTINGNo
COMPLETEDYes
FAILEDYes
UNCONFIRMEDYes (resolved by support)
B.3 failure_reason
failure_reasonMeaning
OTP_INVALIDThe CRA rejected the OTP
OTP_EXPIREDThe OTP window passed before submission
TOKEN_INVALIDThe CRA's OTP token was no longer valid
INSUFFICIENT_BALANCEThe balance does not cover the amount
REJECTEDThe CRA refused the withdrawal for another reason (see message)
UNAVAILABLEThe withdrawal certainly did not reach the CRA
OUTCOME_UNKNOWNUNCONFIRMED only: no answer; it may have been processed
B.4 Balance consent request status
ValueMeaning
PENDING_OTPThe AUTH OTP has been sent
VERIFIEDThe OTP was accepted and the access_key issued
B.5 Problem categories
categoryretryableMeaning
VALIDATION_REJECTEDfalseThe request was understood and refused
AUTH_FAILEDfalseNot authenticated or not authorised
NOT_FOUNDfalseNo such resource for this caller
CONFLICTfalseThe resource is not in a state that allows this
CAPABILITY_UNSUPPORTEDfalseThis operation is not available for this PRAN or registration
RATE_LIMITEDtrueToo many requests
CRA_UNAVAILABLEtrueA recordkeeper could not be reached
CRA_TIMEOUTtrueA recordkeeper did not answer in time
TRANSIENTtrueThe request could not be completed
B.6 Scopes
ScopeAllows
swasthya:eligibilityEligibility (Part I)
swasthya:balanceBalance – Consent Request, Consent Verification and Read (Part II)
swasthya:withdrawWithdrawal – Initiation, Submission and Status (Part III)
Annexures

CNPSC Envelope Reference

An implementation must reproduce these bytes exactly before the first UAT call.

C.1 Test vector
InputValue
encryption key (hex)000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
access tokenpsub.example-token
method, pathPOST, /api/v1/contributions
keyIdjwe_test0000000001
randomNo00112233445566778899aabbccddeeff
timestamp1760261400
iv (hex)a0a1a2a3a4a5a6a7a8a9aaab
body{"clientReferenceId":"pop-ref-1"}
→ K (hex)17e1482ba0ef3637cefd455256cbd12dffe8799f12248678db840005040b5c5e
→ AADNPSC1|REQ|POST|/api/v1/contributions|jwe_test0000000001|00112233445566778899aabbccddeeff|1760261400
→ encDataoKGio6SlpqeoqaqrW6Zcn0RWG8LiZ5MNtPHvfg7HaiEQDE2Ubp2J9D2PUh3toHiTfEm5k5JOHlfxq7Ge1g==
C.2 Node.jsText
const crypto = require('node:crypto');
function seal(key /* Buffer(32) */, keyId, token, method, path, json) {
  const randomNo = crypto.randomBytes(16).toString('hex');
  const timestamp = Math.floor(Date.now() / 1000);
  const info = Buffer.concat([Buffer.from('NPSC1\0'),
    crypto.createHash('sha256').update(token).digest()]);
  const k = Buffer.from(crypto.hkdfSync('sha256', key, Buffer.from(randomNo, 'hex'), info, 32));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', k, iv);
  c.setAAD(Buffer.from(['NPSC1', 'REQ', method, path, keyId, randomNo, timestamp].join('|')));
  const ct = Buffer.concat([c.update(JSON.stringify(json), 'utf8'), c.final()]);
  return { version: 'NPSC1', keyId, randomNo, timestamp,
    encData: Buffer.concat([iv, ct, c.getAuthTag()]).toString('base64') };
}
C.3 Java (JDK 17+)Text
static byte[] hmac(byte[] key, byte[] data) throws Exception {
  Mac m = Mac.getInstance("HmacSHA256"); m.init(new SecretKeySpec(key, "HmacSHA256"));
  return m.doFinal(data);
}
static byte[] deriveKey(byte[] issuedKey, String randomNoHex, String token) throws Exception {
  byte[] prk = hmac(HexFormat.of().parseHex(randomNoHex), issuedKey); // extract
  byte[] tokenHash = MessageDigest.getInstance("SHA-256").digest(token.getBytes(UTF_8));
  ByteBuffer info = ByteBuffer.allocate(6 + 32 + 1)
    .put("NPSC1".getBytes(UTF_8)).put((byte) 0).put(tokenHash).put((byte) 1);
  return hmac(prk, info.array()); // expand, one block
}
static String encData(byte[] k, String aad, String json) throws Exception {
  byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv);
  Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
  c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, "AES"), new GCMParameterSpec(128, iv));
  c.updateAAD(aad.getBytes(UTF_8));
  byte[] ctAndTag = c.doFinal(json.getBytes(UTF_8)); // tag appended
  return Base64.getEncoder().encodeToString(ByteBuffer.allocate(12 + ctAndTag.length)
    .put(iv).put(ctAndTag).array());
}
Annexures

DChange Log

VersionDateChanges
1.021-Oct-2026First issue: eligibility, balance and withdrawal APIs for HBAs.