Document control
- Title
- Account Information — Status, profile, portfolio, returns and transactions
- Version
- 1.0
- Status
- Issued
- Date of issue
- 12 October 2026
- Owner
- NPS Central Engineering
- Classification
- Confidential — for registered partners
1About this document
This document describes the eight APIs that read an existing subscriber's NPS accounts: status, profile, portfolio, holdings, returns, transactions, recent contributions and the contribution tracker. It is written for engineers building account screens, statements or reconciliation on top of the Partner API.
A subscriber may hold accounts at more than one Central Recordkeeping Agency (CRA). NPS Central asks each CRA and combines the answers into one response, and most of what is particular to these APIs follows from that: how a response says that one CRA did not answer, how fields that a CRA does not provide are reported, and how totals are formed. Section 3 explains this and is worth reading before the individual reads.
1.1Notation
In field tables, the presence column shows M for a field that is always present when the account is READY, and O for one that may be absent. Money means a decimal string with two places, such as "184250.37"; units and NAV are decimal strings with four places. The API refers to an existing subscriber as the subject, which is why paths and fields carry names such as subjectRef and subject-links.
2Before you begin
- Credential
- A subscriber session token, from an OTP subject link or an asserted session
- Scope
- subject:read
- Method
- GET, synchronous. No request body and no Idempotency-Key.
- Encryption
- If your account encrypts payloads, responses arrive in the NPSC envelope (application/vnd.npsc.envelope+json); see Payload Encryption.
- Path parameter
- subjectRef — the value returned when the session was opened. Any other value returns 404.
2.1The APIs at a glance
| API | Path (under /v1/subjects/{subjectRef}) | Key in each account | Returns |
|---|---|---|---|
| Account status | /status | status | Each tier's status and the POP / POP-SP it is registered under |
| Profile | /profile | profile | Identity, personal details, contact, addresses, banks, nominees |
| Portfolio | /portfolio | portfolio | Value, contributions, gain or loss, allocation and holdings per tier, plus totals across accounts |
| Holdings | /holdings | holdings | Per-scheme units, NAV and value |
| Returns | /returns | returns | XIRR per account and per tier |
| Transactions | /transactions | transactions | The transaction ledger, paged |
| Recent contributions | /contributions/recent | contributions_recent | The latest credited contributions |
| Contribution tracker | /contributions/tracker | contributions_tracker | Contributions made but not yet converted to units |
2.2Query parameters
| Parameter | Applies to | Description |
|---|---|---|
| pran | All APIs | Returns one account only. The response shape is unchanged; accounts[] has one entry. Must be an account in the session, otherwise 404. |
| tier | All APIs | TIER_1 or TIER_2. On transactions and recent contributions it keeps rows labelled with that tier; if a CRA left rows unlabelled, the tier field is listed in meta.unavailableFields. |
| from, to | Transactions | YYYY-MM-DD. If omitted, to defaults to today and from to one year before to. The window may not exceed 366 days. |
| order | Transactions | NEWEST_FIRST or OLDEST_FIRST: sorts by transactionDate across all accounts before paging. If omitted, each CRA's own order is kept, account by account. |
| limit | Transactions, recent contributions | Page size. Default 25, maximum 100. |
| cursor | Transactions | The previous page's page.nextCursor. |
3How responses are structured
3.1The envelope
Every read has the same outer shape: data.accounts[] holds one entry per account, with the read's data under the key shown in section 2.1, and meta describes the response as a whole.
{
"data": {
"subjectRef": "sub_01JA6Z3KQ8M4N7P2R5S9T1V0WZ",
"accounts": [
{ "pran": "110012345678", "craRef": "cra_7f3a", "craCode": "PROTEAN",
"craName": "Protean eGov Technologies Limited", "state": "READY",
"fetchedAt": "2026-10-09T10:16:02.000Z",
"portfolio": { "…": "the account's data" } },
{ "pran": "400011506418", "craRef": "cra_91c2", "craCode": "KFIN",
"craName": "KFin Technologies Limited", "state": "UNAVAILABLE",
"reason": "SOURCE_TIMEOUT" }
],
"totals": {
"basis": "PARTIAL",
"includedAccounts": [ { "pran": "110012345678", "craRef": "cra_7f3a" } ],
"currentValue": "184250.37", "totalContribution": "150000.00",
"notionalGainLoss": "34250.37"
}
},
"meta": {
"fetchedAt": "2026-10-09T10:16:02.000Z",
"freshness": "LIVE",
"completeness": "PARTIAL",
"unavailableFields": ["portfolio.amountInTransit"]
}
}3.2Account entries
Every account entry, and every row that refers to an account, identifies it the same way. In the field tables that follow, these four are referred to as the account reference.
| Field | Description |
|---|---|
| pran | The account's 12-digit PRAN. |
| craRef | The CRA holding the account: an opaque, stable handle (cra_…). Store and correlate on this. |
| craCode | CAMS, KFIN or PROTEAN. For display. |
| craName | The CRA's registered entity name. For display. |
| state | READY — data present. PENDING — still being fetched; ask again shortly. UNAVAILABLE — see reason. STALE — an earlier answer is being shown. NOT_ASKED — the CRA was not asked for this read. |
| reason | With UNAVAILABLE: NOT_SUPPORTED (permanent), SOURCE_TIMEOUT or SOURCE_ERROR (temporary — retry later), NO_DATA (the CRA answered and holds nothing yet). |
| fetchedAt | When the data was obtained. |
3.3meta, totals and page
| Field | Description |
|---|---|
| meta.fetchedAt | When the response was assembled. |
| meta.freshness | LIVE (asked of the CRA now) or CACHED (answered earlier in this session). STALE_ON_ERROR is used only on the profile read. |
| meta.cacheTtlSeconds | How long the answer may be reused. |
| meta.completeness | COMPLETE, or PARTIAL when any account or field is missing. |
| meta.unavailableFields | Fields left out because a CRA does not provide them, e.g. portfolio.amountInTransit. |
| meta.sources[] | Per account: the account reference, state, reason, fetchedAt, and with PENDING a since timestamp. |
| data.totals | Portfolio read only. Sums across accounts in state READY: basis, includedAccounts[], asOf, currentValue, totalContribution, notionalGainLoss. Rates are never totalled. |
| page | Transactions only. hasMore, nextCursor (absent on the last page) and limit. |
3.4Rules that apply to every read
- Absent, never null or zero. A field a CRA cannot supply is left out of the response and named in meta.unavailableFields. A zero is always a real zero. Do not default missing values to 0 in your own model.
- Money is a string with two decimal places; units and NAV are strings with four. Parse them into a decimal type, not a floating-point number. Rates and percentages are JSON numbers.
- Correlate on craRef, display craCode or craName.
- Asserted sessions see only the accounts the session holds; a session opened by PRAN alone holds that one account. A pran filter naming any other account returns 404.
4The APIs
The tables in this section describe the per-account object, that is data.accounts[].<key>.
4.1Account status
/v1/subjects/{subjectRef}/statusAccount status- Query
- pran, tier
The account inventory: which tiers are open, and which POP / POP-SP each is registered under.
Fields
account referenceobjectRequiredSee section 3.2.
tiers[].tierenumOptionalTIER_1 or TIER_2.
tiers[].statusenumOptionalACTIVE, FROZEN, CLOSED or UNKNOWN. CLOSED also covers accounts the CRA reports as inactive or suspended.
tiers[].freezeReasonstringOptionalWith FROZEN, the reason where known.
tiers[].popobjectOptionalpopRegNo and popSpRegNo (7 digits each): the point of presence and service provider the tier is registered under. Per tier.
4.2Profile
/v1/subjects/{subjectRef}/profileProfile- Query
- pran, tier
The subscriber's details as each account's CRA holds them. PAN, contact details and bank account numbers are masked. Because each CRA keeps its own record, two accounts can show different details for the same person; that is a real difference to resolve with a modification, not a fault in the read.
| Block / field | Description |
|---|---|
| identity | pan (masked), kycMode, kycReference, cersaiId (the CKYC number, masked). |
| person | name, nameParts, dateOfBirth, gender, maritalStatus, fatherName, motherName, spouseName. |
| contact | mobile and email (masked), mobileVerified, emailVerified. An absent verification flag means the CRA does not report it, not false. |
| addresses[] | type (PERMANENT, CORRESPONDENCE, OFFICE), line1–line4, city, stateCode, pinCode, country. Where a CRA delivers the address as one unsplit string, it is in raw instead. |
| banks[] | tier, accountNumber (masked), ifsc, accountType, bankName, verificationStatus, pennyDropVerified. |
| nominees[] | tier, name, relationship, sharePercentage (integers totalling 100 within a tier), dateOfBirth, isMinor, guardianName. |
| sector | rawCode and rawText as the CRA records them; resolved where the CRA publishes a code table. Raw codes are not comparable across CRAs. |
| openedOn | The PRAN generation date, where the CRA states it. |
| sourceExtensions | Fields only one CRA reports. Carried for completeness; do not build logic on them. |
4.3Portfolio
/v1/subjects/{subjectRef}/portfolioPortfolio- Query
- pran, tier
The account's position, with figures per tier, today's allocation, the subscriber's chosen split for future contributions, and holdings. data.totals sums the READY accounts (section 3.3).
Account and tier figures
asOfdatetimeOptionalValuation date. Where it is derived from per-scheme NAV dates, the earliest is used.
currentValuemoneyOptionalValue today.
totalContributionmoneyOptionalContributions to date.
notionalGainLossmoneyOptionalGain or loss. Never computed from a partial contribution history.
totalWithdrawalmoneyOptionalWithdrawn to date. On the account, present only when every tier states it.
amountInTransitmoneyOptionalContributions received but not yet converted to units.
tiers[]arrayOptionalThe same five money fields per tier, plus tier, status and the investment-choice fields.
tiers[].investmentChoicestringOptionalACTIVE, AUTO, LC25, LC50 or LC75, or the CRA's own wording. Absent where the tier's schemes disagree.
tiers[].pfmCodestringOptionalThe pension fund manager running every scheme in the tier.
tiers[].allocation[]arrayOptionalWhat the tier is worth today, by asset class: assetClass (E, C, G, A), percentage and value.
tiers[].schemePreference[]arrayOptionalHow future contributions are split across schemes.
tiers[].holdings[]arrayOptionalPer-scheme holdings (section 4.4). Absent where the CRA reports only aggregates.
4.4Holdings
/v1/subjects/{subjectRef}/holdingsHoldings- Query
- pran, tier
Per-scheme holdings across the account's tiers, as an array under holdings. The same row appears in portfolio.tiers[].holdings[].
Fields
tierstringOptionalTIER_1 or TIER_2.
schemeCodestringRequiredThe scheme's SM… code. Consistent across every CRA: the one reliable join key.
schemeNamestringOptionalAs the CRA writes it. For display only; spellings differ between CRAs.
pfmCode, pfmNamestringOptionalThe pension fund manager. Group by pfmCode.
assetClassstringOptionalUsually E, C, G or A. Not a closed list; passed through unchanged.
unitsobjectOptionaltotal; free and blocked where the CRA states them. Four decimals.
nav, navDateunits, dateOptionalThe NAV used for the valuation and its date.
valuemoneyOptionalCurrent value of the holding.
allocationPercentnumberOptionalThe scheme's share of the tier's current value, one decimal.
4.5Returns
/v1/subjects/{subjectRef}/returnsReturns- Query
- pran, tier
Fields
xirrnumberRequiredRate of return as a decimal fraction, six places: 0.114300 is 11.43 %.
xirrPercentnumberOptionalThe same rate as a percentage.
periodobjectRequiredWhat the rate was measured over: basis (SINCE_INCEPTION, PERIOD, FINANCIAL_YEAR). Always show the period with the rate.
scaleBasisenumRequiredCONFIRMED or ASSUMED: whether the CRA's percentage scale is confirmed or read on a stated assumption.
tiers[]arrayOptionalThe same fields per tier. TIER_2 appears where the account holds one.
4.6Transactions
/v1/subjects/{subjectRef}/transactionsTransactions- Query
- pran, tier, from, to, order, limit, cursor
The ledger, as one list across the account's tiers, paged with page. Each row is a transaction row.
Transaction row
tierstringOptionalTIER_1 or TIER_2 where the CRA states it.
transactionDatedateRequiredWhen the transaction took place.
navDatedateOptionalThe NAV date used.
directionenumRequiredCREDIT or DEBIT.
amountmoneyRequiredAlways positive. Read direction for the sign.
transactionTypestringOptionalThe CRA's type code. Never inferred from the description.
schemeCode, schemeNamestringOptionalThe scheme.
descriptionstringOptionalThe CRA's own description of the row.
openingBalance, closingBalancemoneyOptionalWhere the CRA states them.
4.7Recent contributions
/v1/subjects/{subjectRef}/contributions/recentRecent contributions- Query
- pran, tier, limit
The most recent credited contributions, as transaction rows (section 4.6) under contributions_recent. A contribution appears here only once units have been allotted; for anything earlier, use the tracker.
4.8Contribution tracker
/v1/subjects/{subjectRef}/contributions/trackerContribution tracker- Query
- pran, tier
Contributions that have been made but not yet converted to units. This is the only read that shows a contribution before it settles, so it is the right one for "your payment is being processed" screens.
Fields
tierenumRequiredTIER_1 or TIER_2.
stageenumRequiredPENDING (received; funds not yet confirmed), FRC_UPLOADED (funds confirmed; units not yet allotted), UNITS_CREDITED (done), or UNKNOWN. Branch on this field.
sourceStatusstringOptionalThe CRA's own wording, for display and support.
amountmoneyOptionalThe contribution amount.
requestedOnstringOptionalWhen the contribution was raised.
frcUploadedOnstringOptionalWhen the funds receipt confirmation was uploaded. Absent while PENDING.
unitsCreditedOnstringOptionalWhen units were allotted. Absent until UNITS_CREDITED.
5Errors
Errors are problem documents (application/problem+json). A CRA being unavailable is not an error: it is an account in state UNAVAILABLE inside a 200 response.
| HTTP | code | Cause | What to do |
|---|---|---|---|
| 401 | AUTH_FAILED | No token, a partner token instead of a subscriber session token, an expired or ended session, or no subject:read scope | Open a new subscriber session. |
| 404 | NOT_FOUND | subjectRef is not the session's subscriber, or pran is not an account in the session | Use the values returned when the session was opened. |
| 422 | validation | A malformed query parameter (named in errors[]) | Fix the request. |
| 422 | ORDER_INVALID | order is not NEWEST_FIRST or OLDEST_FIRST | Fix the request. |
| 422 | DATE_RANGE_INVALID | to is before from | Fix the request. |
| 422 | DATE_RANGE_TOO_WIDE | The window spans more than 366 days | Request narrower windows. |
| 429 | RATE_LIMITED | Too many requests | Wait for Retry-After. |
| 503 / 504 | CRA_UNAVAILABLE / CRA_TIMEOUT | The read could not be completed at all | Retry after a pause. |
AImplementation notes
- Use a decimal type for money, units and NAV. Totals computed in floating point will not match the figures NPS Central returns.
- Model absence explicitly. Keep "not provided" distinct from zero, and drive "not available from your CRA" messages from meta.unavailableFields.
- Retry PENDING and temporary UNAVAILABLE accounts, not the whole read. A second request within the session may be served from cache (freshness: CACHED).
- Join schemes on schemeCode and managers on pfmCode. Names are for display and differ between CRAs.
- Display tracker dates as given, and branch on stage rather than sourceStatus.
- Keep the session alive with sessions:refresh while the subscriber is browsing; reads do not extend the idle timeout.