Developers

MaxWealth API reference

Register and sign in investors, take them through KYC and onboarding, set up bank accounts and mandates, place orders on BSE StAR MF, and read portfolios, all from your own app.

Jump to an endpoint
  1. Auth and registration
    1. POST1. Register
    2. POST2. Verify signup OTP and obtain JWT
    3. POST3. Request login OTP
    4. POST4. Verify login OTP and obtain JWT
    5. POST5. MPIN login
    6. POST6.1 Send email OTP
    7. POST6.2 Confirm email OTP
  2. Onboarding V2
    1. POST1.1 Check KYC
    2. POST1.2 Confirm PAN details
    3. GET1.3 Get PAN confirmation record
    4. GET1.4 Get PAN
    5. GET1.5 Get HyperVerge credentials
    6. POST2.1 Add personal details
    7. GET2.2 Get personal details
    8. POST2.3 Add occupation details
    9. GET2.4 Get occupation details
    10. GET2.5 Get onboarding details
    11. POST3.1 Add photo
    12. GET3.2 Get photo
    13. POST3.3 Add PAN photo
    14. POST3.4 Add Aadhaar
    15. GET3.5 Get Aadhaar
    16. POST3.6 Add signature
    17. GET3.7 Get signature
    18. POST4.1 Add address
    19. GET4.2 Get address
    20. POST5.1 Add nominee details
    21. GET5.2 Get nominee details
    22. PATCH5.3 Update nominee details
    23. POST5.4 Generate nominee opt-out OTP
    24. POST5.5 Validate opt-out
    25. POST5.6 Validate nominee OTP
    26. POST6.1 Create UCC
  3. Transaction journey
    1. POST1.1 Add primary bank account
    2. POST1.2 Add a secondary account
    3. GET1.3 Get primary bank account
    4. GET1.4 Get all bank accounts
    5. GET1.5 IFSC lookup
    6. PATCH1.6 Update primary account
    7. PATCH1.7 Update bank details
    8. POST2.1 Create mandate
    9. POST2.2 Get the eNACH authorization link
    10. GET3.1 Fund lookup
    11. POST4.1 Create basket
    12. POST4.2 Send consent OTP
    13. POST4.3 Submit to BSE
    14. POST4.4 Collect payment (lumpsum and one-off only)
    15. GET4.5 Read back basket items
  4. Portfolio holdings and dashboard
    1. GET1.1 Dashboard totals
    2. GET2.1 Holdings
    3. GET3.1 Portfolio analysis
    4. GET4.1 Returns graph
    5. GET4.2 Benchmark NAV graph
    6. GET5.1 Monthly capital-gains report
    7. GET5.2 Yearly capital-gains report
    8. GET5.3 Download capital-gains report
    9. GET6.1 Paginated order lists
    10. GET6.2 Status shortcut routes
    11. GET6.3 Plan detail routes

Last updated 11 August 2026

Reference for the MaxWealth platform API: user registration and login, onboarding (KYC, KRA, e-sign and UCC), the transaction journey (bank accounts, mandates and transaction baskets), and portfolio holdings and the dashboard.

Every request carries a tenant_id header that identifies the distributor. Authenticated calls also carry Authorization: Bearer <access_token>, issued at the end of the login flow.

Examples use {{base_url}} as a placeholder for your environment's host.

Protected
Requires Authorization: Bearer <access_token> and tenant_id.
Session
Send Authorization and tenant_id on every call. The caller's identity is read from these headers.
Public
No bearer token. Only the tenant_id header is needed.

Auth and registration

The calls a client makes before touching any onboarding or transaction-basket endpoint.

Base paths: /api/users and /api/auth.

  • A tenant_id header is required on every request, including these auth routes. A missing header returns 403 No Tenant Sent, and an unknown tenant_id returns 403.
  • tenant_id is a static value, one per distributor, provisioned to a client the same way a mobile app has it in its build config.
  • Mobile OTP is the primary credential path. Email/password and Google or Apple OAuth sign-in also exist but require a browser-based flow.
  • The response envelope is mostly { status, data | message | error }, matching Onboarding V2. A few routes in this module return the user record spread into the top-level object instead; this is noted where it happens.
  • Email verification is a separate step that runs alongside this sequence. See Email OTP verification below.
  • Every authenticated call needs both Authorization and tenant_id. The JWT is validated against the tenant in the header, so both must match the tenant the user was created under.
  • JWT payload: { user: { id, email, full_name, role, token_time, last_failed, last_successful_login, sub_role, sub_broker_code } }.
  • There is no token-refresh endpoint in this version. When access_token expires, run the OTP login flow again (steps 3 and 4).
New number   → POST /api/users             (register)
             → POST /api/auth/verifymobile (verify signup OTP → JWT issued)

Known number → POST /api/auth/generate_otp (request OTP)
             → POST /api/auth/verify_otp   (verify → JWT issued)

Either path ends with an access_token + refresh_token.
Everything after this point (onboarding, bank, mandates, transaction-baskets)
is called with `Authorization: Bearer <access_token>` + the same `tenant_id` header.

JWT details

TokenLifetime for investors (role User)Lifetime for staff roles
access_token1d3h
refresh_token7d1d

Registration

For a brand-new mobile number, start with registration. generate_otp is the returning-user login route and expects the user to exist already.

POST/api/userstenant_id only

1. Register

Creates the user record and sends the signup OTP over SMS, WhatsApp and/or email, depending on the tenant's notification preferences.

No Authorization header: this is the entry point.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyCreateUserDto

NameTypeRequiredNotes
full_namestringYes
emailstringYes
mobilestringYes
country_codestringNoDefaults per tenant if omitted
is_leadbooleanNo
fcmTokenstringNoPush-notification token, optional
referral_codestringNo
Sample request
{
  "full_name": "Rahul Sharma",
  "email": "rahul@example.com",
  "mobile": "9876543210",
  "country_code": "+91"
}
200 · User created
{
  "status": 200,
  "id": 1,
  "full_name": "Rahul Sharma",
  "email": "rahul@example.com",
  "mobile": "9876543210",
  "mpin": null,
  "otp": null,
  "country_code": "+91",
  "mobile_verified": false,
  "is_active": true,
  "is_blocked": false
}
422 · Mobile or email already belongs to a fully verified user
{ "status": 422, "error": "A user with this mobile number already exists" }
  • Keep id from this response. You need it for the next call (verifymobile). The OTP is delivered only by SMS, WhatsApp or email and never appears in the response body.
POST/api/auth/verifymobiletenant_id only

2. Verify signup OTP and obtain JWT

Verifies the signup OTP sent at registration and issues the session tokens.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyVerifyMobileDto

NameTypeRequiredNotes
user_idstringYes
otpnumberYes
fcmTokenstringNo
Sample request
{ "user_id": "1", "otp": 482913 }
200 · Verified, tokens issued
{
  "status": 200,
  "id": 1,
  "full_name": "Rahul Sharma",
  "email": "rahul@example.com",
  "mobile": "9876543210",
  "mobile_verified": true,
  "otp": null,
  "access_token": "<jwt>",
  "refresh_token": "<jwt>"
}
400 · OTP mismatch or expired
{ "status": 400, "error": "Invalid OTP" }
  • On the first successful verification, a one-time welcome notification goes out.
  • Store both access_token and refresh_token against the user for the duration of the session.

Login for returning users

POST/api/auth/generate_otptenant_id only

3. Request login OTP

Sends a login OTP to a returning user's registered mobile number.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyGenerateOtpDto

NameTypeRequiredNotes
mobilestringYes
is_generatebooleanNo
200 · OTP sent (delivered by SMS, WhatsApp or email per tenant preference)
{ "status": 200 }
400 · No such user
{ "status": 400, "error": "User not found, please register" }
  • Treat the 400 response as the signal to fall back to registration (step 1) instead of retrying login.
POST/api/auth/verify_otptenant_id only

4. Verify login OTP and obtain JWT

Verifies the login OTP and issues the session tokens.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyVerifyOtpDto

NameTypeRequiredNotes
mobilestringYes
otpnumberYes
fcmTokenstringNo
200 · Same shape as verifymobile: full user fields plus access_token and refresh_token
{
  "status": 200,
  "id": 1,
  "full_name": "Rahul Sharma",
  "email": "rahul@example.com",
  "mobile": "9876543210",
  "mobile_verified": true,
  "otp": null,
  "access_token": "<jwt>",
  "refresh_token": "<jwt>"
}
400 · Invalid OTP
{ "status": 400, "error": "Invalid OTP" }
POST/api/auth/mpin_loginBearer token

5. MPIN login

A follow-up login route for returning app users who have already set an MPIN. It mirrors the mobile app's quick-unlock behaviour and requires an existing valid JWT.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyMpinDto

NameTypeRequiredNotes
mpinstringYes
Sample request
{ "mpin": "1234" }
  • A 200 response returns a fresh access_token and refresh_token.

6. Email OTP verification

Alongside mobile verification, a separate OTP track confirms the user's email address. Treat it as a required step in any new client integration (including a WhatsApp flow): call generate_email_otp and verify_email as part of onboarding, alongside mobile verification.

Registration (POST /api/users) prepares an email OTP but does not send it. Call generate_email_otp to trigger delivery, then verify_email to confirm it.

verify_email confirms the email address but does not issue a new session token. Mobile OTP (or MPIN) remains the way to authenticate.

Signing in with Google or Apple OAuth also marks the email as verified, since those flows confirm the address independently.

POST/api/auth/generate_email_otptenant_id only

6.1 Send email OTP

Sends an OTP to the user's email address.

Rate limit: 3 requests per 60 seconds.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyGenerateEmailOtpDto

NameTypeRequiredNotes
emailstringYes
is_generatebooleanNoDefaults to true
200 · OTP sent
{ "status": 200 }
400 · No user with this email
{ "status": 400, "error": "User not found, please register" }
403 · Blocked account
{ "status": 403, "error": "Your account has been blocked" }
  • Delivery is by email only. This OTP has no SMS or WhatsApp fallback.
  • The code expires a few minutes after generation. A retry with an expired code fails the same way as an incorrect one, so call generate_email_otp again to get a fresh code.
POST/api/auth/verify_emailtenant_id only

6.2 Confirm email OTP

Confirms the email OTP.

Rate limit: 10 requests per 60 seconds.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor

BodyVerifyEmailDto

NameTypeRequiredNotes
user_idstringYes
otpnumberYes
Sample request
{ "user_id": "1", "otp": 482913 }
200 · Email verified
{
  "status": 200,
  "id": 1,
  "full_name": "Rahul Sharma",
  "email": "rahul@example.com",
  "mobile": "9876543210",
  "mobile_verified": true,
  "is_email_verified": true
}
400 · OTP mismatch, or no such user
{ "status": 400, "error": "Invalid OTP" }
  • This route returns the full user record, so expect a few more fields than shown above. Key off status and is_email_verified rather than the full field list.
  • The first time an email is verified, a one-time welcome email goes out.

Onboarding V2

Every endpoint in the V2 onboarding flow, with request and response shapes and behavioural notes: KYC and PAN, personal and occupation details, document uploads, address, nominee, and the investment account (UCC) that completes onboarding.

  • Base path: /api/v2/.
  • All examples use {{base_url}} as a placeholder for your environment's host.
  • Almost every response body follows { status: <http status repeated in body>, data | message | error }. The HTTP status code on the wire matches the status field in the body.
  • The tenant_id header identifies the distributor and is required on nearly every route.

1. KYC and PAN

POST/api/v2/onboarding/check_kycBearer token

1.1 Check KYC

Checks whether a PAN is already KYC-compliant with the KRA, creating an onboarding record either way.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyCheckKycDto

NameTypeRequiredNotes
user_idnumberYesRegistered user's ID
panstringYes10-character PAN
Sample request
POST /api/v2/onboarding/check_kyc
tenant_id: your_tenant_id
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "user_id": 1,
  "pan": "ABCDE1234F"
}
200 · Not yet KYC compliant
{
  "status": 200,
  "data": {
    "name": "Rahul Sharma",
    "pan": "ABCDE1234F",
    "status": false,
    "user_id": 1
  }
}
200 · Already KYC compliant, PAN registry details attached
{
  "status": 200,
  "data": {
    "name": "Rahul Sharma",
    "pan": "ABCDE1234F",
    "status": true,
    "user_id": 1,
    "pan_details": {
      "aadhaar_seeding_status": true,
      "category": "Individual",
      "first_name": "RAHUL",
      "full_name": "RAHUL SHARMA",
      "last_name": "SHARMA",
      "last_updated": "23/09/2018",
      "middle_name": "",
      "name_on_card": "",
      "pan_number": "ABCDE1234F",
      "pan_status_code": "E",
      "pan_status_detail": "EXISTING AND VALID",
      "pan_title": "Shri"
    }
  }
}
400 · Error
{ "status": 400, "error": "sorry something went wrong, <message>" }
  • data.status (boolean) is the actual KYC-compliance flag. The HTTP status is 200 either way.
POST/api/v2/onboarding/confirm_pan_detailsBearer token

1.2 Confirm PAN details

Confirms PAN and Aadhaar identity fields for a user who has already passed the KYC check.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyConfirmPanDetailsDto

NameTypeRequiredNotes
user_idnumberYes
panstringYes
aadhar_numberstringYes
full_namestringYes
date_of_birthdate stringYes
Sample request
{
  "user_id": 1,
  "pan": "ABCDE1234F",
  "aadhar_number": "123456789012",
  "full_name": "Rahul Sharma",
  "date_of_birth": "1990-01-01"
}
200 · Details updated
{ "status": 200, "message": "Updated the details" }
400 · No prior check_kyc call for this user
{ "status": 400, "error": "Please check your KYC compliance first" }
GET/api/v2/onboarding/confirm_pan_detailsBearer token

1.3 Get PAN confirmation record

Fetches the KYC/PAN record created by check_kyc.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
Sample request
GET /api/v2/onboarding/confirm_pan_details?user_id=1
tenant_id: your_tenant_id
Authorization: Bearer <jwt>
200 · Record found
{
  "status": 200,
  "data": {
    "id": 1,
    "full_name": "rahul sharma",
    "date_of_birth": "1990-01-01T00:00:00.000Z",
    "pan": "abcde1234f",
    "user_id": 1,
    "kyc_id": "kycr_xxxxxxxxxxxxxxxx",
    "is_kyc_compliant": false
  }
}
404 · No onboarding record
{ "status": 404, "error": "No Onboarding found" }
GET/api/v2/onboarding/get_panBearer token

1.4 Get PAN

Fetches the stored PAN details and verification state for a user.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · PAN details
{
  "status": 200,
  "message": "PAN details retrieved successfully",
  "data": {
    "user_id": 123,
    "pan_number": "ABCDE1234F",
    "masked_name": "S**** R****",
    "verified": true,
    "uploaded_at": "2025-07-01T12:34:56.789Z"
  }
}
400 · Missing or invalid user_id
{ "status": 400, "error": "sorry something went wrong, user_id is missing or invalid" }

1. KYC and PAN: digital KYC when the PAN is not yet compliant

When check_kyc (1.1) returns data.status: false, the investor completes digital KYC through HyperVerge before moving on to personal details. This step also covers the KRA (KYC Registration Agency) application status.

KYC and KRA results reach MaxWealth from the KYC provider automatically; poll get_onboarding_details (2.5) to see the updated status. Once it reports success, continue to Personal and occupation details as normal.

GET/api/v2/onboarding/get_hyperverge_credsSend token and tenant_id

1.5 Get HyperVerge credentials

Issues (or reuses) a HyperVerge transaction and returns the SDK credentials the frontend needs to launch the HyperVerge KYC widget.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · New transaction
{
  "status": 200,
  "data": {
    "appId": "<your-app-id>",
    "appKey": "<your-app-key>",
    "jwtToken": "<jwt>",
    "transactionId": "txn_xxxxxxxxxxxxxxxx",
    "uniqueId": "101",
    "workflowId": "default",
    "status": "Started",
    "inputs": {
      "mobileNumber": null,
      "email": null,
      "panNumber": null,
      "dateOfBirth": null,
      "kraStatus": "new",
      "kyc_status": "new"
    }
  }
}
404 · User not found
{ "status": 404, "message": "User not found" }

2. Personal and occupation details

POST/api/v2/onboarding/add_personal_detailsBearer token

2.1 Add personal details

Saves the investor's family, marital and gender details.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyAddPersonalDetailsDto

NameTypeRequiredNotes
user_idnumberYes
father_namestringYes
mother_namestringYes
marital_statusstringYes
genderstringYes
Sample request
{
  "user_id": 1,
  "father_name": "Ramesh Sharma",
  "mother_name": "Sunita Sharma",
  "marital_status": "Single",
  "gender": "Male"
}
200 · Details updated
{ "status": 200, "message": "Updated the details" }
400 · KYC check not done
{ "status": 400, "error": "Please check your KYC compliance first" }
GET/api/v2/onboarding/get_personal_detailsBearer token

2.2 Get personal details

Returns the saved personal details.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Personal details
{
  "status": 200,
  "data": {
    "id": 1,
    "father_name": "ramesh sharma",
    "mother_name": "sunita sharma",
    "marital_status": "married",
    "gender": "male",
    "user_id": 1,
    "kyc_id": "kycr_xxxxxxxxxxxxxxxx",
    "is_kyc_compliant": false
  }
}
404 · No onboarding record
{ "status": 404, "error": "No Onboarding found" }
POST/api/v2/onboarding/add_occupation_detailsBearer token

2.3 Add occupation details

Saves occupation, income, nationality and politically-exposed-person status.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyAddOccupationDetailsDto

NameTypeRequiredNotes
user_idnumberYes
occupationstringYes
annual_incomestringYes
nationalitystringYes
is_political_personbooleanNoDefaults to false
Sample request
{
  "user_id": 1,
  "occupation": "Software Engineer",
  "annual_income": "10-15 LPA",
  "nationality": "Indian",
  "is_political_person": false
}
200 · Details updated
{ "status": 200, "message": "Updated the details" }
400 · KYC check not done
{ "status": 400, "error": "Please check your KYC compliance first" }
GET/api/v2/onboarding/get_occupation_detailsBearer token

2.4 Get occupation details

Returns the saved occupation details.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Occupation details
{
  "status": 200,
  "data": {
    "id": 1,
    "annual_income": "above_1cr",
    "nationality": "IN",
    "occupation": "business",
    "user_id": 1,
    "kyc_id": "kycr_xxxxxxxxxxxxxxxx",
    "is_kyc_compliant": false
  }
}
404 · No onboarding record
{ "status": 404, "error": "No Onboarding found" }
GET/api/v2/onboarding/get_onboarding_detailsBearer token

2.5 Get onboarding details

Returns the full onboarding record: KYC, personal, occupation, document URLs, and e-sign, UCC and investment-account state.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Truncated to representative fields; the real payload returns the full onboarding record
{
  "status": 200,
  "data": {
    "id": 3,
    "is_kyc_compliant": true,
    "pan": "ABCDE1234F",
    "full_name": "Rahul Sharma",
    "date_of_birth": "2005-12-12T00:00:00.000Z",
    "father_name": "Ramesh Sharma",
    "mother_name": "Sunita Sharma",
    "marital_status": "unmarried",
    "gender": "male",
    "occupation": "business",
    "annual_income": "above_1lakh_upto_5lakh",
    "nationality": "indian",
    "signature_url": "uploads/signature/xxxxxxxxxxxxxxxx.jpg",
    "photo_url": null,
    "status": "nominee",
    "fp_investor_id": null,
    "fp_investment_account_id": null,
    "fp_kyc_status": "pending",
    "user_id": 3,
    "created_at": "2024-11-28T04:13:55.000Z",
    "updated_at": "2024-11-28T05:26:22.000Z",
    "type": "individual",
    "tax_status": "individual",
    "mobile": "9876543210",
    "email": "rahul@example.com",
    "is_investment_done": false
  }
}
404 · No onboarding record
{ "status": 404, "error": "No Onboarding found" }

3. Document uploads

All upload endpoints take multipart/form-data. The file field name is always photo, whatever the document actually is (PAN photo, Aadhaar or signature). Use photo as the field name for every route in this section, including signature (3.6).

POST/api/v2/onboarding/add_photoBearer token

3.1 Add photo

Uploads the user's selfie.

Content type: multipart/form-data

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
photofileYes
user_idnumberYes
Sample request
curl -X POST '{{base_url}}/api/v2/onboarding/add_photo' \
  -H 'tenant_id: your_tenant_id' \
  -H 'Authorization: Bearer <jwt>' \
  -F 'photo=@selfie.jpg' \
  -F 'user_id=1'
200 · Uploaded
{ "status": 200, "message": "Updated the details" }
400 · No file attached
{ "status": 400, "error": "sorry something went wrong, No file uploaded" }
GET/api/v2/onboarding/get_photoBearer token

3.2 Get photo

Returns the stored selfie URL.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Photo record
{
  "status": 200,
  "data": {
    "id": 1,
    "fp_photo_file_id": "file_xxxxxxxxxxxxxxxx",
    "photo_url": "https://files.example.com/uploads/user_1_photo.jpg",
    "user_id": 1,
    "kyc_id": "kycr_xxxxxxxxxxxxxxxx",
    "is_kyc_compliant": false
  }
}
404 · No onboarding record
{ "status": 404, "error": "No Onboarding found" }
POST/api/v2/onboarding/add_pan_photoBearer token

3.3 Add PAN photo

Uploads a photo of the physical PAN card.

Content type: multipart/form-data

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
photofileYes
user_idnumberYes
Sample request
curl -X POST '{{base_url}}/api/v2/onboarding/add_pan_photo' \
  -H 'tenant_id: your_tenant_id' -H 'Authorization: Bearer <jwt>' \
  -F 'photo=@pan_card.jpg' -F 'user_id=1'
200 · Uploaded
{ "status": 200, "message": "Updated the details" }
400 · KYC not started or no onboarding record
{ "status": 400, "error": "Please check your KYC compliance first" }
400 · Initial step incomplete
{ "status": 400, "error": "Please do the process from the initial step" }
  • On success, this advances onboarding to the next step: digilocker status when a matching verified Aadhaar record already exists, or pan_image status otherwise.
POST/api/v2/onboarding/add_aadharBearer token

3.4 Add Aadhaar

Uploads an image of the Aadhaar card.

Content type: multipart/form-data

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
photofileYes
user_idnumberYes
Sample request
curl -X POST '{{base_url}}/api/v2/onboarding/add_aadhar' \
  -H 'tenant_id: your_tenant_id' -H 'Authorization: Bearer <jwt>' \
  -F 'photo=@aadhar_card.jpg' -F 'user_id=123'
200 · Uploaded
{
  "status": 200,
  "message": "Aadhar photo uploaded",
  "data": { "user_id": 123, "photo_url": "https://files.example.com/aadhar/user123.png" }
}
400 · No file attached
{ "status": 400, "error": "No file uploaded" }
GET/api/v2/onboarding/get_aadharBearer token

3.5 Get Aadhaar

Returns the stored Aadhaar details.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Aadhaar details
{
  "status": 200,
  "message": "Aadhar details retrieved successfully",
  "data": {
    "user_id": 123,
    "aadhar_number": "XXXX-XXXX-1234",
    "masked_name": "S**** R****",
    "verified": true,
    "uploaded_at": "2025-07-01T12:34:56.789Z"
  }
}
POST/api/v2/onboarding/add_signatureBearer token

3.6 Add signature

Uploads the user's signature image.

Content type: multipart/form-data

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
photofileYesSee the note on field naming at the top of this section
user_idnumberYes
Sample request
curl -X POST '{{base_url}}/api/v2/onboarding/add_signature' \
  -H 'tenant_id: your_tenant_id' -H 'Authorization: Bearer <jwt>' \
  -F 'photo=@signature.png' -F 'user_id=1'
200 · Uploaded
{ "status": 200, "message": "Updated the details" }
400 · No file, or wrong field name
{ "status": 400, "error": "No file uploaded — Check your field name (photo/file)" }
GET/api/v2/onboarding/get_signatureBearer token

3.7 Get signature

Returns the stored signature URL.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Signature details
{
  "status": 200,
  "message": "Signature details retrieved successfully",
  "data": {
    "user_id": 123,
    "signature_url": "https://files.example.com/signatures/123.jpg",
    "uploaded_at": "2025-07-01T12:34:56.789Z"
  }
}

4. Address

POST/api/v2/onboarding/addressBearer token

4.1 Add address

Saves the investor's address. Use the field names line_1, line_2 and line_3 as listed below.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyAddAddressDetailsDto

NameTypeRequiredNotes
user_idnumberYes
line_1stringYes
line_2stringNo
line_3stringNo
citystringYes
statestringYes
pincodestringYes
Sample request
{
  "user_id": 101,
  "line_1": "Plot 45, Green Street",
  "line_2": "Near Central Mall",
  "line_3": "",
  "city": "Bangalore",
  "state": "Karnataka",
  "pincode": "560076"
}
200 · Address added
{
  "status": 200,
  "message": "Address added successfully",
  "data": {
    "user_id": 101,
    "line_1": "Plot 45, Green Street",
    "line_2": "Near Central Mall",
    "line_3": "",
    "city": "Bangalore",
    "state": "Karnataka",
    "pincode": "560076"
  }
}
400 · Missing field
{ "status": 400, "error": "sorry something went wrong, line_1 is missing" }
GET/api/v2/onboarding/addressBearer token

4.2 Get address

Returns the saved address.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberNoIf omitted, falls back to the authenticated user
Sample request
GET /api/v2/onboarding/address?user_id=101
tenant_id: your_tenant_id
Authorization: Bearer <jwt>
200 · Address
{
  "status": 200,
  "message": "Address details retrieved successfully",
  "data": {
    "user_id": 101,
    "line_1": "Plot 45, Green Street",
    "line_2": "Near Central Mall",
    "line_3": "",
    "city": "Bangalore",
    "state": "Karnataka",
    "pincode": "560076"
  }
}
400 · Missing or invalid user_id
{ "status": 400, "error": "sorry something went wrong, user_id is missing or invalid" }

5. Nominee, path A: add nominee details

At this step, decide whether the investor will have a nominee. Complete exactly one of the two paths: submit nominee details (path A), or opt out by OTP (path B). Either path updates the records the UCC step reads when it submits the investor's account to BSE.

Path A submits the nominee's actual details: name, relationship, date of birth and allocation share. Its base path is /api/nominee.

POST/api/nomineeBearer token

5.1 Add nominee details

Body: an array of nominee objects, up to 3 nominees per investor.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
user_idYes
nameYes
date_of_birthYes
relationshipYese.g. Spouse, Father, Mother, Son, Daughter
allocation_percentageYesMust sum to exactly 100 across all nominees in the array
guardian_name / guardian_relationship / guardian_panNoRequired if the nominee is under 18
panNo
aadhar_numberNo
passport_number / driving_licence_numberNoAlternative identity proof
email_addressNo
isd / phone_numberNo
address_line_1 / address_line_2 / address_line_3No
address_city / address_state / address_country / address_postal_codeNo
Sample request
[
  {
    "user_id": 101,
    "name": "Priya Sharma",
    "date_of_birth": "1994-03-12",
    "relationship": "Spouse",
    "allocation_percentage": 100
  }
]
200 · Saved
{ "status": 200, "message": "Saved Successfully" }
400 · Validation failed, e.g. allocations not summing to 100, a duplicate relationship such as two Spouse entries, or missing guardian details for a minor
{ "status": 400, "error": "<validation message>" }
  • Send the investor's complete list of nominees in one call. The array replaces the existing nominee set rather than adding to it, so a later call with a shorter list removes the nominees left out.
GET/api/nomineeBearer token

5.2 Get nominee details

Returns the investor's nominees.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
200 · Nominee list
{
  "status": 200,
  "data": [
    {
      "id": 1,
      "name": "Priya Sharma",
      "date_of_birth": "1994-03-12",
      "relationship": "Spouse",
      "allocation_percentage": 100,
      "user_id": 101
    }
  ]
}
PATCH/api/nomineeBearer token

5.3 Update nominee details

Same body shape as adding a nominee. Use this to submit a revised nominee list; the same full-replace behaviour applies.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>
200 · Saved
{ "status": 200, "message": "Saved Successfully" }

5. Nominee, path B: opt out of a nominee

Use this path when the investor chooses not to add a nominee.

Send Authorization on every call in this path, because the caller's identity is read from it. If the caller's role is ADMIN, user_id is taken from the request body (acting on behalf of another user). For any other role, the target user is always the caller.

POST/api/v2/onboarding/generate_nominee_otpSend token and tenant_id

5.4 Generate nominee opt-out OTP

Sends a 6-digit OTP (SMS, WhatsApp or email, per the tenant's channel configuration) to confirm the opt-out.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
user_idstringNoOnly used when the caller is ADMIN
Sample request
{ "user_id": "101" }
200 · OTP sent
{ "status": 200, "message": "Otp sent successfully" }
400 · No onboarding record
{ "status": 400, "error": "User Onboarding Not Found" }
400 · Initial step incomplete
{ "status": 400, "error": "Please do the process from the initial step" }
POST/api/v2/onboarding/validate_optoutSend token and tenant_id

5.5 Validate opt-out

Validates the OTP and opts the user out of the nominee requirement (is_nominee_opted = false).

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
user_idstringNoOnly used when the caller is ADMIN
otpnumberYes
Sample request
{ "user_id": "101", "otp": 482913 }
200 · Opted out
{ "status": 200, "message": "Congratulations Nominee has been opted out" }
400 · OTP mismatch
{ "status": 400, "error": "Otp does not match" }
400 · No onboarding record
{ "status": 400, "error": "User Onboarding not found" }
POST/api/v2/onboarding/validate_nomineeSend token and tenant_id

5.6 Validate nominee OTP

Validates the OTP and toggles the current is_nominee_opted flag, where validate_optout always sets it to false. Use this for a generic confirm-this-OTP flow that can flip either direction, and use validate_optout when the intent is specifically to opt out.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
user_idstringNoOnly used when the caller is ADMIN
otpnumberYes
Sample request
{ "user_id": "101", "otp": 482913 }
200 · Flag toggled
{
  "status": 200,
  "message": "Nominee has been opted out successfully.",
  "data": { "is_nominee_opted": false }
}
400 · OTP mismatch
{ "status": 400, "error": "OTP does not match" }
400 · OTP not generated
{ "status": 400, "error": "OTP not generated" }

6. Investment account (UCC)

Creating the UCC is the last step of onboarding. Call it only after personal details, occupation, address, document uploads and the nominee step (details or opt-out) are all in place, and after a primary bank account has been added (see Add primary bank account under Transaction journey).

The UCC submission reads back everything from those earlier steps to build the account it registers with BSE, so completing them first is what makes this call produce a complete account.

POST/api/v2/onboarding/add_ucc_v2Send token and tenant_id

6.1 Create UCC

Creates the BSE UCC (Unique Client Code) and completes onboarding. This is the final step of the flow.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
user_idnumberYes
Sample request
{ "user_id": 101 }
200 · Onboarding complete
{
  "status": 200,
  "data": {
    "id": 101,
    "status": "ucc",
    "fp_investor_id": "1234567890123456",
    "is_onboarding_complete": true,
    "fp_kyc_status": "verified"
  }
}
400 · UCC creation rejected by BSE, FATCA submission rejected, or AOF PDF/password extraction failed
{ "status": 400, "message": "<BSE remarks / FATCA message / Failed to Proccess AOF upload>" }
  • On success, this sends an onboarding-completed notification and a one-time WhatsApp confirmation message.
  • Nominee fields are populated from the address and nominee records when is_nominee_opted is true.

Transaction journey

Reference for placing an investment transaction (lumpsum, SIP, SWP, STP, switch or redemption) against BSE StAR MF once a user has completed onboarding. It covers bank accounts, mandates and transaction baskets.

Transaction baskets are the order-placement API. Bank accounts and mandates are its two prerequisites.

  • A tenant_id header is required on every route.
  • Response envelope: { status, data | message | error }, the same convention as Onboarding V2.
  • Order status updates arrive from the exchange automatically; read them through get_basket_items and the orders endpoints under Portfolio holdings and dashboard.
1. POST /api/transaction-baskets                         → creates basket + items
2. POST /api/transaction-baskets/generate-consent        → sends OTP to the user (SMS/WhatsApp/Email)
3. POST /api/transaction-baskets/validate-basket-consent → OTP verified → orders submitted to BSE
4. POST /api/transaction-baskets/initiate-basket-payment → (lumpsum/one-off only) collect payment via UPI/netbanking
   SIP/SWP/STP funded by a mandate skip step 4; the mandate itself authorizes future debits.
5. Final BSE order status arrives automatically; read it back with
   GET /api/transaction-baskets/get_basket_items or the orders endpoints.

Which base path is which

Base pathWhat it's for
/api/transaction-basketsThe order-placement API. Create a basket, add items, get consent (OTP), submit to BSE, pay.
/api/transactionsRead-only reporting over settled RTA transaction history. Use it for a transaction-history screen, not for placing an order.
/bulk-transaction (no /api prefix)An admin bulk-upload flow (one Excel file creates baskets for many users in one operator action). It is separate from the per-user order flow described here.

Prerequisite 1: Bank account

A verified bank account is required before a mandate can be created (SIP, SWP and STP funding) or a lumpsum payment can be collected.

POST/api/bankBearer token

1.1 Add primary bank account

Adds the user's bank account and marks it primary if it is the first one.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyAddUserBankDetailsDto

NameTypeRequiredNotes
user_idYes
account_holder_nameYes
account_numberYes
ifsc_codeYes
bank_nameYes
branch_nameNoAuto-filled from IFSC lookup if omitted
bank_city / bank_stateNoAuto-filled from IFSC lookup if omitted
proofNo
vpa_idNoUPI VPA, needed later for UPI-based payments
200 · Account added
{
  "status": 200,
  "data": {
    "id": 55,
    "account_holder_name": "Rahul Sharma",
    "account_number": "XXXXXXXX1234",
    "bank_name": "HDFC Bank",
    "ifsc_code": "HDFC0000001",
    "branch_name": "MG Road",
    "bank_city": "Bangalore",
    "bank_state": "Karnataka",
    "is_penny_drop_success": true,
    "is_penny_drop_attempted": true,
    "is_primary": true
  }
}
400 · No onboarding record yet
{ "status": 400, "error": "Please check your KYC compliance first" }
  • On success, a penny-drop validation runs (setting is_penny_drop_success and is_penny_drop_attempted), the account is marked is_primary if it is the user's first, and the account is synced to BSE.
POST/api/bank/add_additional_bankBearer token

1.2 Add a secondary account

Same body and response contract as the primary-account endpoint above. It does not change which account is primary.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>
GET/api/bankBearer token

1.3 Get primary bank account

Returns the user's primary bank account.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idYes
GET/api/bank/allBearer token

1.4 Get all bank accounts

Returns every bank account for the user, each including its linked mandates: [...]. Use this endpoint when you need mandate status per account.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idYes
GET/api/bank/ifscBearer token

1.5 IFSC lookup

Looks up branch details for an IFSC code from a public IFSC directory. Useful for validating and auto-filling branch details before adding a bank account.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
ifscYes
PATCH/api/bank/update_primary_accountBearer token

1.6 Update primary account

Makes this account primary. The previous primary account is no longer marked as such.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>
Sample request
{ "id": 55 }
PATCH/api/bank/update_bank_detailsBearer token

1.7 Update bank details

Same fields as adding a bank account, plus a required id.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Prerequisite 2: Mandate (required before SIP, SWP and STP)

A mandate is an eNACH auto-debit authorization tied to one bank account. Skip this section if you are only building lumpsum and redemption flows.

POST/api/mandatesBearer token

2.1 Create mandate

Creates an eNACH mandate against one of the user's bank accounts.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyAddMandateDto

NameTypeRequiredNotes
bank_idstringYesMust belong to the user, from Prerequisite 1
mandate_limitnumberYesMaximum amount BSE can debit per transaction under this mandate
mandate_typestringNoShould match a value BSE recognizes (e.g. XSIP)
Sample request
{ "bank_id": "55", "mandate_limit": 50000, "mandate_type": "XSIP" }
200 · Mandate created
{
  "status": 200,
  "data": {
    "id": 12,
    "mandate_id": "MND123456",
    "bank_id": "55",
    "status": "created",
    "mandate_limit": 50000
  },
  "bse_mandate_id": "MND123456"
}
400 · An active mandate already exists on this bank account
{ "status": 400, "error": "An active mandate already exists for this bank account." }
  • The returned mandate_id cannot be used for a SIP order yet. It becomes usable once the user completes eNACH authorization (next step) and BSE marks it approved.
POST/api/mandates/authorizeBearer token

2.2 Get the eNACH authorization link

Returns the hosted eNACH authorization link. Only proceeds if the mandate's current status is created.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Body

NameTypeRequiredNotes
mandate_idstringYes

2.3 Mandate status values

StatusMeaning
createdMandate created; the user has not yet completed eNACH authorization
submittedUser completed eNACH authorization; BSE is processing
approvedReady to use as the payment_source for a SIP, SWP or STP order
rejectedAuthorization was rejected
Sample request
{ "mandate_id": "MND123456" }
200 · Authorization link
{
  "status": 200,
  "data": {
    "mandate_id": "MND123456",
    "status": "created",
    "token_url": "https://payments.example.com/enach/authorize?..."
  },
  "bse_mandate_id": "MND123456"
}
  • token_url is a hosted bank/BSE page. Redirect the user there (for example, a "tap here to authorize auto-debit with your bank" link) rather than rendering it inline.
  • The user completes authorization on their bank's own OTP or net-banking flow, and the mandate status then moves to approved, rejected or submitted. Poll GET /api/bank/all and check the mandates[] relation to pick up the change.
  • This call can take up to a minute to respond while it waits on the upstream authorization link, so set a generous client-side timeout.

Prerequisite 3 (optional): Fund lookup

GET/api/transaction-baskets/fundBearer token

3.1 Fund lookup

Returns fund metadata for building a basket item and validating amounts client-side before submitting: min_initial_investment, purchaseAllowed, redemptionAllowed, switchInAllowed / switchOutAllowed, sip_allowed / swp_allowed, lock_in / lock_in_period, min_withdrawal_amount / min_withdrawal_units, and per-frequency breakdowns.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
isinstringYes
200 · Fund metadata
{
  "status": 200,
  "data": {
    "isin": "INF109K01Z48",
    "min_initial_investment": 1000,
    "purchaseAllowed": true,
    "sip_allowed": true,
    "sip_frequency_specific_data": {
      "monthly": {
        "min_installment_amount": 500,
        "max_installment_amount": 100000,
        "amount_multiples": 100,
        "min_installments": 6,
        "dates": [1, 5, 10, 15, 20, 25]
      }
    },
    "swp_frequency_specific_data": { "...": "same shape" },
    "stp_frequency_specific_data": { "...": "same shape" }
  }
}
  • Use this to validate the user's amount and frequency before submitting a basket. It saves a round trip compared with discovering a bad amount at consent time.

4. Order placement

The order-placement lifecycle is shown at the top of this section. Each step is documented below.

POST/api/transaction-basketsBearer token

4.1 Create basket

Creates a transaction basket and its items.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyCreateTransactionBasketDTO

NameTypeRequiredNotes
user_idstringYes
goal_idnumberNo
user_goal_idstring (UUID)NoOptional
model_portfolio_idnumberNo
total_amountnumberYes
is_redemption_fullbooleanNo
is_euinbooleanNofalse suppresses the tenant's EUIN on this order
is_bulkbooleanNoLeave false; true is for the admin bulk-upload path
consent_emailstringNo
transaction_basket_itemsCreateTransactionBasketItemDTO[]YesSee the item table below

CreateTransactionBasketItemDTO

FieldNotes
transaction_typeOne of: lumpsum, sip, redemption, swp, switch_fund, stp
fund_isinRequired
folio_numberFor existing-folio transactions
amount / unitsamount for purchase-type, units for redemption by units
frequency, installment_day, number_of_installmentsFor sip, swp and stp
to_fund_isinTarget scheme for switch_fund
payment_methode.g. UPI, NETBANKING
payment_sourceFor SIP, SWP and STP, the mandate id from Prerequisite 2
generate_first_installment_nowboolean
Sample request
{
  "user_id": "101",
  "goal_id": 5,
  "model_portfolio_id": 3,
  "total_amount": 5000,
  "is_redemption_full": false,
  "is_euin": true,
  "is_bulk": false,
  "consent_email": "rahul@example.com",
  "transaction_basket_items": [
    {
      "transaction_type": "lumpsum",
      "fund_isin": "INF109K01Z48",
      "amount": 5000,
      "payment_method": "UPI"
    }
  ]
}
200 · Basket created
{
  "status": 200,
  "data": {
    "id": "8f2a...-uuid",
    "user_id": 101,
    "total_amount": 5000
  }
}
400 · Onboarding not complete
{ "status": 400, "message": "Please complete the onboarding account creation." }
  • After creating a basket, fetch the full item details (including generated item ids) with GET /api/transaction-baskets/get_basket_items rather than relying on the create response for item-level fields.
  • For redemption items, the BSE order payload is built and saved at this step, ahead of the other transaction types. The consent step then submits that same payload.
POST/api/transaction-baskets/generate-consentBearer token

4.2 Send consent OTP

Sends a consent OTP to the user by SMS, WhatsApp and/or email, depending on tenant preference.

Rate limit: 3 requests per minute.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
transaction_basket_idstringYes
typestringYes
200 · OTP generated
{ "status": 200, "message": "OTP generated" }
404 · Basket not found
{ "status": 404, "error": "Basket not found" }
POST/api/transaction-baskets/validate-basket-consentBearer token

4.3 Submit to BSE

Verifies the consent OTP and submits the orders to BSE, branching on each item's transaction_type as shown in the table.

Rate limit: 3 requests per minute.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyValidateConsentBodyDto

NameTypeRequiredNotes
transaction_basket_idstringYes
otpnumberYes

What happens per transaction type

transaction_typeBSE callResulting item status
lumpsumPurchase/redemption order (BuySellP)submitted / failed
sipXSIP registration + first installmentactive / failed
redemptionResubmits the payload built when the basket was createdsuccessful / failed
swpSWP registration + first installmentactive / failed
switch_fundSwitch ordersuccessful / failed
stpSTP registration + first installmentactive / failed
Sample request
{ "transaction_basket_id": "8f2a...", "otp": 482913 }
200 · Orders submitted
{
  "status": 200,
  "data": {
    "id": "8f2a...",
    "is_consent_verified": true,
    "transaction_basket_items": [
      {
        "id": 42,
        "transaction_type": "lumpsum",
        "status": "submitted",
        "purchases": [{ "order_no": "BSE123456", "response_message": "..." }]
      }
    ]
  }
}
400 · OTP mismatch
{ "status": 400, "message": "OTP does not match" }
  • For swp, a successful registration is indicated by successFlag == '100'. Every other transaction type in the table uses '0' for success, so branch on the right value per type.
  • Each successful item triggers a notification (purchase placed, redemption placed, switch placed or STP successful) over the tenant's configured channels, independent of this API response.
  • We recommend keeping SIP baskets to a single item. A registration issue with one basket is then unambiguous and does not affect the processing of items in other baskets.
  • If the referenced mandate does not exist or is not approved yet, this call fails with a generic error message. For SIP, SWP and STP, check mandate status with GET /api/bank/all before reaching this step.
POST/api/transaction-baskets/initiate-basket-paymentBearer token

4.4 Collect payment (lumpsum and one-off only)

Starts payment for the basket. Skip this for SIP, SWP and STP, where the mandate authorizes the debits directly.

method decides which BSE bank-code lookup runs (NETBANKING or UPI).

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

BodyPaymentTransactionBasketDTO

NameTypeRequiredNotes
transaction_basket_idstringYes
methodstringYesUPI or NETBANKING
bank_idstringYes
vpa_idstringNoUPI VPA
Sample request
{
  "transaction_basket_id": "8f2a...",
  "method": "UPI",
  "bank_id": "55",
  "vpa_id": "rahul@upi"
}
200 · Payment link
{
  "status": 200,
  "data": {
    "token_url": "https://payments.example.com/pay?token=...",
    "statuscode": "100"
  }
}
400 · BSE rejected the request
{ "status": 400, "message": "<BSE responsestring>" }
  • Redirect the user to token_url, the same pattern as the mandate authorization link. It is a hosted BSE payment page and should not be embedded inline.
  • Only basket items that have completed consent are included in the payment total. Confirm with get_basket_items that the items you expect to pay for show is_payment: true and is_consent_verified: true.
GET/api/transaction-baskets/get_basket_itemsBearer token

4.5 Read back basket items

The endpoint for reading full item details after creating or updating a basket, including each item's latest order status.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
transaction_basket_idstringYes
200 · Basket items
{
  "status": 200,
  "data": [
    {
      "id": 42,
      "transaction_type": "lumpsum",
      "fund_isin": "INF109K01Z48",
      "amount": 5000,
      "status": "submitted",
      "is_payment": true
    }
  ]
}

Portfolio holdings and dashboard

Endpoints for showing a user their investments: dashboard totals, holdings, asset allocation, returns graphs, capital-gains reports, and pending or recent orders.

  • A tenant_id header is required on every route, as everywhere else in this API.
  • Some routes accept a user_id query parameter. An investor can pass only their own id; Admin, SubBroker and SuperAdmin users can pass another investor's id. See Orders below for details.
  • The response envelope varies by route: some return data, some return result, and a couple return fields at the top level. Each endpoint below shows the real shape.

Which endpoint answers which question

QuestionWhere to lookNotes
What's my total invested, current value and today's change?GET /api/portfolio/dashboardSee Dashboard below.
What do I currently hold, fund by fund?GET /api/portfolio/v2/holdingsSee Holdings below.
How is my portfolio split by category, sector, market cap and stock?GET /api/portfolio/portfolio_analysisComputed live from current holdings. See Portfolio analysis below.
Show me a returns-over-time chartGET /api/portfolio/returns_graphSee Returns graphs below.
Give me a downloadable capital-gains reportGET /api/portfolio/capital_gain_report*Pre-generated report rows plus a download link. See Capital-gains reports below.
What orders are pending or recently placed?/api/order-status/*Around 25 routes, one per transaction type and status. See Orders below for the shared response shapes.
What's the current NAV, scheme name and AMC logo for an ISIN?Included in the responses aboveSee the closing note at the end of this section.

1. Dashboard

GET/api/portfolio/dashboardBearer token

1.1 Dashboard totals

No query parameters. Always resolves to the logged-in user.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>
200 · Dashboard totals
{
  "status": 200,
  "data": {
    "mf_investment_account": 101,
    "user_id": "101",
    "invested_amount": 50000,
    "current_value": 54230.5,
    "unrealized_gain": 4230.5,
    "absolute_return": 8.46,
    "cagr": 12.1,
    "xirr": 0,
    "day_change": 120.3,
    "day_change_percentage": 0.22,
    "total_returns": 4230.5,
    "total_returns_percentage": 8.46
  }
}
400 · User has no investment account yet
{ "status": 400, "error": "Please complete onboarding to view your dashboard." }
  • xirr is not yet computed here and is always returned as 0. Use cagr or absolute_return for a growth figure in the meantime.
  • current_value and unrealized_gain can be returned as either a number or a numeric string, depending on whether the user has an existing returns snapshot. Parse them with Number(...) on the client.

2. Holdings

GET/api/portfolio/v2/holdingsBearer token

2.1 Holdings

Returns the user's scheme holdings with NAV, market value and invested value.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberNoDefaults to the logged-in user
filterstringNo
tradedOnTostringNoYYYY-MM-DD
pagenumberNo
limitnumberNo
200 · Holdings
{
  "status": 200,
  "data": {
    "id": 101,
    "folios": [
      {
        "isin": "INF109K01Z48",
        "name": "Example Bluechip Fund",
        "folio_number": "12345678/90",
        "holdings": { "as_on": "2026-08-10", "units": 120.456, "redeemable_units": 120.456 },
        "market_value": { "as_on": "2026-08-10", "amount": 5423.05, "redeemable_amount": 5423.05 },
        "invested_value": { "as_on": "2026-08-10", "amount": 5000 },
        "nav": { "as_on": "2026-08-10", "value": 45.02 },
        "logo_url": "https://files.example.com/amc-logo.png",
        "plan_id": 12345,
        "amc_id": 67
      }
    ],
    "meta": { "totalItems": 14, "page": 1, "limit": 20, "totalPages": 1 },
    "excelDownloadLink": "https://files.example.com/holdings_101.xlsx?expires=3600"
  }
}
404 · No investment account
{ "status": 404, "error": "Investment account not created" }
  • data.folios is a flat list of individual scheme holdings. Each row carries its own folio_number, so group them client-side if you want a folio-first view.
  • The endpoint is role-aware: a SubBroker sees only their referred investors' holdings, an Admin sees every folio in the tenant, and everyone else sees only their own.
  • Each call also refreshes an Excel export of the result and returns a download link (excelDownloadLink) alongside the JSON.

3. Portfolio analysis (asset allocation)

GET/api/portfolio/portfolio_analysisBearer token

3.1 Portfolio analysis

Returns allocation by category, sector, market cap and stock, computed live from current holdings.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberNoDefaults to the logged-in user
tradedOnTostringNo
200 · Allocation breakdown
{
  "id": 101,
  "folios": ["... same shape as the holdings endpoint above ..."],
  "data": {
    "total_investments": 50000,
    "total_absolute_return": 8.46,
    "fund_analysis_variables": { "INF109K01Z48": 5423.05 },
    "category_base_allocation": { "Large Cap": 62.5, "Mid Cap": 37.5 },
    "sector_base_alloction": { "Financial Services": 28.1, "IT": 19.4 },
    "cap_base_allocation": { "giant": 40.2, "large": 22.3, "mid": 25.1, "small": 8.9, "tiny": 3.5 },
    "stock_base_allocation": { "HDFC Bank Ltd.": 12.4, "Infosys Ltd.": 9.8 }
  }
}
404 · No investment account
{ "status": 404, "error": "Investment account not created" }
400 · No investments
{ "status": 400, "error": "You don't have any investments" }
  • The field is sector_base_alloction, which is the actual spelling returned by the API.
  • Allocation figures are computed live from current holdings, looking up allocation detail for each holding individually. For portfolios with many distinct funds, allow a little extra time for this call.

4. Returns graphs

GET/api/portfolio/returns_graphBearer token

4.1 Returns graph

Returns a time series of the user's portfolio value and returns. This always reflects the logged-in user's own graph.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
durationnumberYese.g. number of days to look back
200 · Returns series
{
  "status": 200,
  "data": [
    {
      "date": "2026-07-11",
      "invested_amount": 48000,
      "current_value": 51200,
      "unrealized_gain": 3200,
      "absolute_return": 6.67,
      "cagr": 11.2,
      "xirr": 9.8,
      "addjusted_value": 51200,
      "day_change": 90.1
    }
  ]
}
  • The field is addjusted_value, which is the actual spelling returned by the API.
GET/api/portfolio/benchmark_nav_graphBearer token

4.2 Benchmark NAV graph

A NAV time series for the tenant's configured benchmark fund, for plotting alongside the user's own returns graph.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
durationnumberYes
200 · Benchmark NAV series
{ "status": 200, "data": [ { "date": "2026-07-11", "nav": 152.34 } ] }

5. Capital-gains reports

These serve pre-generated report rows: monthly through 5.1 and yearly through 5.2.

GET/api/portfolio/capital_gain_reportBearer token

5.1 Monthly capital-gains report

Returns monthly capital-gains report rows.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
pagenumberNoDefaults to 1
200 · Report rows
{
  "status": 200,
  "data": [
    {
      "id": 55,
      "financial_year": "2025-26",
      "month": 4,
      "short_term_gain": 1200.5,
      "long_term_gain": 340.2,
      "report_url": "https://{{base_url}}/api/portfolio/capital-gain-report/55/download"
    }
  ]
}
  • report_url is generated fresh on every response and always points at the download endpoint (5.3).
GET/api/portfolio/capital_gain_report_yearlyBearer token

5.2 Yearly capital-gains report

Returns yearly capital-gains report rows, in the same shape as 5.1.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
pagenumberNoDefaults to 1
  • report_url is generated fresh on every response and always points at the download endpoint (5.3).
GET/api/portfolio/capital-gain-report/:id/downloadBearer token

5.3 Download capital-gains report

Streams the report as an Excel file with Content-Disposition: attachment.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>
404 · Report not found
{ "status": 404, "error": "Report not found" }

6. Orders: pending, active and recent

The orders API has one route per transaction type (lumpsum, SIP, switch, redemption, SWP, STP) crossed with a status shortcut (completed, in-progress, failed, active, pending or inactive, depending on the type). This section covers the shared response shapes and gives a route table, which is enough to build a pending-orders or recent-activity widget against any of them.

All paths are relative to /api/order-status.

Access control: on the paginated and shortcut routes, an investor (role User) can pass only their own user_id. Admin, SubBroker and SuperAdmin callers can look up any user_id, which powers distributor and back-office views of an investor's orders.

The three detail routes key off a plan id (fp_sip_id / fp_swp_id / fp_stp_id) rather than user_id. Build client features around them with that in mind, and do not pass around or expose plan ids where a different user could see them.

Conceptually similar fields (such as a switch or redemption's target-fund name and logo) are sometimes named slightly differently between routes for the same transaction type. Read the sample for the specific route you are integrating against rather than assuming a name from a neighbouring one.

GET/api/order-status/lumpsumBearer token

6.1 Paginated order lists

Paginated list routes: /lumpsum, /sip, /switches, /redemption, /swp and /stp. All share this shape.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYes
typestringNoStatus filter. Each route has a sensible default list of statuses if omitted
pagenumberNo
limitnumberNo

6.4 Route table (relative to /api/order-status)

TypePaginated listShortcutsDetail
Lumpsum/lumpsum?type=/completed_lumpsum, /inprogress_lumpsum, /failed_lumpsumNone
SIP/sip?type=/active_sips, /pending_sips, /inactive_sips/get_sip_details?fp_sip_id=
Switch/switches?type=/completed_switches, /inprogress_switches, /failed_switchesNone
Redemption/redemption?type=/completed_redemption, /inprogress_redemption, /failed_redemptionNone
SWP/swp?type=/pending_swps, /active_swps, /inactive_swps/get_swp_details?fp_swp_id=
STP/stp?type=/pending_stps, /active_stps, /inactive_stps/get_stp_details?fp_stp_id=
200 · Paginated rows
{
  "status": 200,
  "data": [ "... order rows ..." ],
  "meta": { "page": 1, "limit": 20, "total": 8, "totalPages": 1 }
}
GET/api/order-status/active_sipsBearer token

6.2 Status shortcut routes

One route per status per type, for example /completed_lumpsum, /active_sips, /pending_swps and /inactive_stps. See the route table for the full list.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
user_idnumberYesThe only query parameter
200 · Unpaginated rows
{ "status": 200, "data": [ "... order rows, unpaginated ..." ] }
GET/api/order-status/get_sip_detailsBearer token

6.3 Plan detail routes

Detail routes: get_sip_details, get_swp_details and get_stp_details. Each takes a plan id.

Headers

NameRequiredNotes
tenant_idYesIdentifies the distributor
AuthorizationYesBearer <access_token>

Query parameters

NameTypeRequiredNotes
fp_sip_id / fp_swp_id / fp_stp_idstringYesThe plan id for the route
200 · Plan detail
{
  "status": 200,
  "data": {
    "id": 42,
    "mandate": { "mandate_id": "MND123456" },
    "installments": [ "... related purchase/redemption rows ..." ]
  }
}
  • If nothing matches, you get data: [] back rather than a 404. Check for an empty array rather than relying on the status code alone.

Where NAV, scheme name and AMC logo data comes from

Every current value, NAV, scheme name and logo field in this section is filled in by MaxWealth's mutual-fund data service at request time. The same service supplies category and plan details for the allocation and returns calculations, the benchmark NAV chart, and the sector, market-cap and stock breakdowns.

A separate cached table of BSE scheme rules (cut-off times, exit load, SIP and lock-in flags) supports order validation and is not used for these display fields.