NPS Zaruri Hai
Technical document

Authentication – Client secret with subject assertion

How a point of presence obtains tokens and opens subscriber sessions

How a point of presence registered with a client secret obtains an access token, opens a session for an existing subscriber on its own signed assertion, and manages that session.

Version
Version 1.0
Status
Final
Last updated
Updated 12/10/2026
Audience:
Registered partners (points of presence)
Prepared by:
NPS Central Engineering
Document control
Title
Authentication — Client secret with subject assertion
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 is for engineers integrating a point of presence (POP) with the NPS Central Partner API using a client secret (client_secret_basic or client_secret_post). It covers the two credentials every integration needs: the partner access token, which identifies your organisation, and the subscriber session, which lets you act for one existing subscriber.

You open a subscriber session on your own subject assertion. You verify and authenticate the subscriber in your own application, then present a signed statement of who they are; NPS Central confirms with the CRA that the account is registered under your point of presence and opens the session. The subscriber's identity is verified at your end, not by NPS Central.

Once you can open a session, the account reads are described in Account Information, changes to a subscriber's details in Modifications, and encrypting request and response bodies in Payload Encryption.

1.1Notation

In field tables, the presence column shows M for mandatory, O for optional, and C for conditional — mandatory only in the situation the description gives. Paths are relative to the base URL for your environment. Examples use $BASE_URL for that value and shorten long tokens with an ellipsis. The API refers to an existing subscriber as the subject, which is why paths and fields carry names such as subjectRef and subject_ref.

2Before you begin

NPS Central sets up your partner account and gives you the following. Keep them to hand; every step in this document uses at least one of them.

ItemNotes
Client IDFor example pop-acme. Not secret, but identifies you in every token request and is the iss of your assertions.
Client secretDelivered once, out of band. Store it in a secrets manager; never in source control or client-side code.
Authentication methodEither client_secret_basic or client_secret_post. Your account accepts only the method it was registered for.
Encryption key and key IDA 256-bit key, delivered once, out of band, as base64url text, with its key ID. It signs your subject assertions, and encrypts request and response bodies if your account is set up for payload encryption.
POP registrationsYour POP / POP-SP codes at each CRA, recorded against your account. Only accounts registered under them can be reached.
Token endpoint URLThe exact URL to use as aud in your assertions, e.g. https://uat.example.in/api/v1/auth/partner-token.
Base URLOne per environment (UAT and Production). Always ends in /api.
ScopesThe scopes your account holds (section 8). Your tokens can never carry more.

3How authentication works

There are two credentials, and they answer different questions.

CredentialProvesHow you get itLifetime
Partner access tokenWho you areClient credentials grant (section 5)15 minutes
Subscriber session tokenThat you verified and authenticated the subscriber, and the CRA confirms the account is under your POPSubject assertion grant (section 6)15 minutes idle, 60 minutes maximum

The partner token is used for work that is not about one existing subscriber: eligibility checks, onboarding, registration, and reading the status of contributions and modifications you submitted. Everything about one existing subscriber — account reads, contributions, modifications and documents — uses the subscriber session token.

4Conventions for every call

4.1Requests

AspectRule
TransportHTTPS only.
AuthorisationAuthorization: Bearer <token> on every API call.
Body formatapplication/json, or the NPSC envelope (application/vnd.npsc.envelope+json) if your account encrypts payloads. The token endpoint is the exception: always application/x-www-form-urlencoded, never encrypted.
CorrelationEvery response carries an x-request-id header. If you send your own x-request-id, it is echoed back. Quote it in any support request.
DatesYYYY-MM-DD. Timestamps are RFC 3339 in UTC with a Z suffix.
Mobile numbersE.164: +91 followed by 10 digits, the first of which is 6–9.

4.2Errors

Apart from the token endpoint, every error is an RFC 9457 problem document with content type application/problem+json.

Problem documentJSON
{
  "type": "https://api.npscentral.in/problems/validation-rejected",
  "title": "The request was understood and refused.",
  "status": 422,
  "category": "VALIDATION_REJECTED",
  "code": "MODIFICATION_REQUEST_INVALID",
  "detail": "The request body does not match the modification type.",
  "retryable": false,
  "requestId": "3f6c1a9e-…",
  "errors": [
    { "field": "pop[0].popRegNo", "code": "MODIFICATION_REQUEST_INVALID", "message": "…" }
  ]
}
FieldUse it for
categoryYour error handling logic. One of VALIDATION_REJECTED, AUTH_FAILED, NOT_FOUND, CONFLICT, CAPABILITY_UNSUPPORTED, RATE_LIMITED, CRA_UNAVAILABLE, CRA_TIMEOUT, TRANSIENT.
codeA finer-grained reason within the category, for logs and specific messages. New codes may be added; do not fail on an unknown one.
retryableWhether repeating the same request may succeed.
errors[]On validation failures, one entry per field at fault (field, code, message).
requestId, diagnosticRefQuote these to support.

A 429 response carries a Retry-After header; wait at least that long before retrying. The token endpoint follows OAuth 2.0 instead and returns { "error": "<code>" } (section 5.4).

5Partner access token

The partner token identifies your organisation. You use it for calls that are not about one existing subscriber, and to follow contributions and modifications you submitted. Opening a subscriber session does not need it: the assertion grant authenticates you with your client credentials directly (section 6).

5.1Endpoint

POST/v1/auth/partner-tokenPartner access token
Grant
OAuth 2.0 client credentials (RFC 6749 §4.4). The same endpoint takes the subject assertion grant (section 6).
Content type
application/x-www-form-urlencoded
Encryption
Never
Rate limit
30 requests per 5 minutes, per client ID and per source address

5.2Request

Request parameters
  • grant_type
    formRequired

    client_credentials

  • scope
    formOptional

    Space-separated scopes to request, e.g. subject:read contribution. Omit to receive every scope you hold.

  • Authorization
    headerConditional

    client_secret_basic only: Basic base64(urlencode(client_id) ":" urlencode(client_secret))

  • client_id
    formConditional

    client_secret_post only: your client ID

  • client_secret
    formConditional

    client_secret_post only: your client secret

Present the secret in one place only. A request with both a Basic header and a client_secret field is refused, as is any request that puts the secret in the query string.

client_secret_basicHTTP
curl -X POST "$BASE_URL/v1/auth/partner-token" \
  -u "pop-acme:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d scope="subject:read contribution modification"

5.3Response — 200 OK

Response body
  • access_token
    stringRequired

    The partner token. Treat it as opaque; do not decode or rely on its contents.

  • token_type
    stringRequired

    Always Bearer.

  • expires_in
    integerRequired

    Seconds until expiry. Currently 900 (15 minutes).

  • scope
    stringRequired

    The scopes this token carries, space-separated.

The response is sent with Cache-Control: no-store.

5.4Errors

HTTPerrorCauseWhat to do
400unsupported_grant_typegrant_type missing or not one of the two grantsFix the request.
400invalid_requestNo credentials; credentials in two places; malformed Basic header; secret in the query stringFix the request.
400invalid_scopeNone of the requested scopes is one you holdRequest scopes you hold, or omit scope.
401invalid_clientAny authentication failure: unknown client, wrong or revoked secret, wrong method, suspended account, disallowed source addressCheck the client ID, secret, method and environment. If they are right, contact support with the x-request-id.
429slow_downRate limit exceededBack off and cache tokens (section 5.5).

invalid_client deliberately does not say which check failed, so that the response gives nothing away to someone guessing credentials.

5.5Handling the token

  • Cache it. Reuse the token for its lifetime and request a new one when fewer than 60 seconds remain. Requesting a token per API call will hit the rate limit.
  • Recover once. If an API returns 401 AUTH_FAILED, obtain a new token and retry the call once. If it fails again, stop and investigate rather than looping.
  • Expect changes to take effect immediately. Your account is checked on every call, so a suspension or a removed scope applies to tokens you already hold.

6Opening a subscriber session

Every subscriber session is opened on your own subject assertion: a short JWT, signed with your encryption key, in which you state who the subscriber is and how you authenticated them. NPS Central sends no OTP. It asks the CRA holding each account which POP / POP-SP the account is under, and keeps only the accounts registered under your registrations at that CRA.

POST/v1/auth/partner-tokenOpen subscriber session (subject assertion)
Grant
JWT bearer (urn:ietf:params:oauth:grant-type:jwt-bearer)
Prerequisites
The subject:assert grant; your POP registrations on file; a live encryption key, which signs the assertion
Content type
application/x-www-form-urlencoded
Encryption
Never
Rate limit
600 per hour per partner, and 5 per hour per partner per subscriber

6.1Verifying the subscriber

NPS Central relies on your assertion and does not verify the subscriber itself. Before you present one, you must have:

  • verified the subscriber's identity at your end, to the standard your agreement with NPS Central requires — for example against their KYC record and the account details you hold;
  • authenticated them in this interaction, for example with an OTP to their registered mobile number, a password and OTP, or a biometric, no more than 15 minutes before the request; and
  • obtained their consent to act for them.

The assertion records how you authenticated them (amr) and when (auth_time). Both must be accurate.

6.2Identifying the subscriber

You assertWhat NPS Central doesThe session holds
Mobile number and date of birthFinds the subscriber at every CRA, then asks the CRA holding each account which POP / POP-SP it is under. Add pran to narrow to one of the subscriber's accounts.Every account of the subscriber's that is under your registrations. accounts_incomplete is true if a CRA did not answer.
PRAN aloneFinds the CRA holding the PRAN and asks it which POP / POP-SP the account is under.That one account, with a subject_ref of its own that is new for each session.

6.3The subject assertion

A compact JWS, signed HS256 with your encryption key: the 32 bytes you obtain by decoding the key's base64url value. Never sign with your client secret.

HeaderReq.Value
algMHS256. No other algorithm is accepted.
kidMThe key ID of the encryption key you signed with. It must be a live key of yours.
typOJWT
Claims
  • iss
    stringRequired

    Your client ID.

  • sub
    stringRequired

    Your own reference for the subscriber, such as your customer ID. Never your client ID. Recorded against the session.

  • aud
    stringRequired

    The token endpoint URL exactly as given to you, character for character.

  • jti
    stringRequired

    A unique value, such as a UUID. Each value is accepted once.

  • iat
    integerRequired

    Issued-at time, epoch seconds. Not in the future.

  • exp
    integerRequired

    Expiry, epoch seconds. No more than 300 seconds after iat.

  • subject
    objectRequired

    Either { mobile, dateOfBirth }, optionally with pran, or { pran } alone.

  • amr
    arrayRequired

    How you authenticated them, as RFC 8176 values: for example ["otp"], ["pwd","otp"], ["fpt"], ["face"].

  • auth_time
    integerRequired

    When you authenticated them, epoch seconds. No more than 15 minutes before this request, and not in the future.

  • pop[]
    arrayOptional

    Limits the check to particular registrations of yours. Each entry is { cra, popRegNo, popSpRegNo }. Omit pop to accept any of your registered pairs.

Example claimsJSON
{
  "iss": "pop-acme",
  "sub": "cust-88213",
  "aud": "https://uat.example.in/api/v1/auth/partner-token",
  "jti": "a7d9e1c4-2b6f-4a83-8e15-0c3f9b7d2e61",
  "iat": 1791547200,
  "exp": 1791547320,
  "subject": { "pran": "110012345678" },
  "amr": ["otp"],
  "auth_time": 1791547180
}

6.4Request

Request parameters
  • grant_type
    formRequired

    urn:ietf:params:oauth:grant-type:jwt-bearer

  • assertion
    formRequired

    The subject assertion (section 6.3).

  • Authorization
    headerConditional

    client_secret_basic only: your client credentials, as in section 5.2.

  • client_id, client_secret
    formConditional

    client_secret_post only: your client credentials, as in section 5.2.

  • scope
    formOptional

    Narrows the session: any of subject:read, contribution, modification and document.

6.5Response — 200 OK

Response body
  • access_token
    stringRequired

    The subscriber session token. Send it as Authorization: Bearer <access_token> on subscriber APIs.

  • token_type
    stringRequired

    Always Bearer.

  • expires_in
    integerRequired

    Seconds until the session's absolute end, 60 minutes after the assertion. Refreshing does not move it.

  • idle_expires_in
    integerRequired

    Seconds until the session ends if it is not refreshed, normally 900 (15 minutes). Call POST /v1/auth/sessions:refresh to extend it.

  • scope
    stringRequired

    At most subject:read contribution modification document, limited to the scopes you hold.

  • subject_ref
    stringRequired

    The subscriber's subjectRef, used in /v1/subjects/{subjectRef}/… paths. On a session opened by PRAN alone it is new for each session; correlate on the PRAN.

  • accounts[]
    arrayRequired

    The accounts the session holds: pran, craRef, craCode, craName.

  • accounts_incomplete
    booleanConditional

    Present, and true, when a CRA did not answer; an account may be missing. Never present on a session opened by PRAN alone.

6.6Errors

HTTPerrorCause
400invalid_grantAnything about the subscriber or the subject assertion: no match for the mobile and date of birth; a PRAN no CRA holds; no account under your POP; a pop pair you do not hold; auth_time or amr missing or out of range; a bad signature, aud or lifetime; a replayed jti; your client ID in sub.
400unauthorized_clientsubject:assert is not on your registration, or kid does not name a live encryption key of yours.
400invalid_scopescope names nothing an asserted session can carry.
400invalid_requestNo assertion, or client credentials in more than one place.
401invalid_clientYour client credentials failed (section 5.4).
429slow_downAssertion rate limit exceeded.
503temporarily_unavailableNo account could be confirmed, or the PRAN could not be found, because a CRA did not answer.

6.7Your responsibilities

Under your agreement with NPS Central, you are responsible for having verified and authenticated the subscriber, and obtained their consent, before presenting an assertion. Keep the jti, sub, amr and auth_time of every assertion you send; NPS Central records the same values, and they are what either side will refer to if a session is ever questioned. Because the same encryption key signs your assertions and encrypts your payloads, revoking it stops both.

7Managing the session

These calls take the subscriber session token.

RequestPurpose
GET /v1/sessions/currentCheck that the session is live and see what it holds.
POST /v1/auth/sessions:refreshExtend the idle timeout (section 7.1). No request body.
POST /v1/auth/sessions:logoutEnd the session. No request body.

7.1Session lifetime

Idle timeoutAbsolute lifetime
15 minutes, extended only by sessions:refresh60 minutes from the assertion

Ordinary API calls do not extend the idle timeout. Call refresh when the subscriber is active in your application, rather than on a timer. Refreshing never moves the session past its 60 minutes. A session also ends early if the subject:assert grant is withdrawn from your account.

7.2Fresh OTP for sensitive changes

CONTACT_UPDATE, BANK_UPDATE and TIER2_ACTIVATION move either the subscriber's money or the channel every later code is sent to, so each needs your own fresh OTP. Immediately before filing, authenticate the subscriber with an OTP that your application sends, and state the result in the request's verification, with a verifiedAt no more than 5 minutes old. This is separate from the authentication your assertion records. A missing or stale verification is refused with 422 PARTNER_VERIFICATION_REQUIRED.

8Scopes and credentials

8.1Scopes

ScopeGrants
eligibilityPAN validation and duplicate checks
onboardingCreating and completing onboarding applications
registrationSubmitting registrations and reading their status
subject:readAccount reads on a subscriber session
contributionContributions on a subscriber session; their status on the partner token
modificationModifications on a subscriber session; their status on the partner token
documentDocument requests on a subscriber session
subject:assertThe subject assertion grant (section 6). Never carried on a token.

A call outside your scopes returns 401 AUTH_FAILED, the same response as an invalid token. If a freshly issued token receives it, check the scope field returned with that token.

8.2Which credential each API takes

APIPartner tokenSubscriber session
/v1/eligibility/…, /v1/onboarding-applications/…, /v1/registrations…Yes–
GET /v1/sessions/current, …/sessions:refresh, …/sessions:logout–Yes
GET /v1/subjects/{subjectRef}/… (account reads)–Yes
POST /v1/contributions, …:retry–Yes
POST /v1/subjects/{subjectRef}/modification-requests, …:acknowledge, …:cancel–Yes
GET /v1/contributions/{id}, GET /v1/modification-requests/{id}Yes (your own)Yes
/v1/document-jobs/…–Yes
GET /v1/reference/…Not requiredNot required

Records belong to the partner that created them. Another partner's record, a record the subscriber created elsewhere, and an identifier that does not exist all return the same 404 NOT_FOUND.