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.
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.
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
Token
Lifetime for investors (role User)
Lifetime for staff roles
access_token
1d
3h
refresh_token
7d
1d
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.
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.
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
BodyMpinDto
Name
Type
Required
Notes
mpin
string
Yes
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
BodyGenerateEmailOtpDto
Name
Type
Required
Notes
email
string
Yes
is_generate
boolean
No
Defaults 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.
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.
{ "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.
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).
{ "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.
{ "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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Body
Name
Type
Required
Notes
user_id
Yes
name
Yes
date_of_birth
Yes
relationship
Yes
e.g. Spouse, Father, Mother, Son, Daughter
allocation_percentage
Yes
Must sum to exactly 100 across all nominees in the array
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
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.
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Body
Name
Type
Required
Notes
user_id
string
No
Only 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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Body
Name
Type
Required
Notes
user_id
string
No
Only used when the caller is ADMIN
otp
number
Yes
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Body
Name
Type
Required
Notes
user_id
string
No
Only used when the caller is ADMIN
otp
number
Yes
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.
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 path
What it's for
/api/transaction-baskets
The order-placement API. Create a basket, add items, get consent (OTP), submit to BSE, pay.
/api/transactions
Read-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.
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
GET/api/bankBearer token
1.3 Get primary bank account
Returns the user's primary bank account.
Headers
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Query parameters
Name
Type
Required
Notes
user_id
Yes
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Query parameters
Name
Type
Required
Notes
user_id
Yes
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Query parameters
Name
Type
Required
Notes
ifsc
Yes
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
BodyAddMandateDto
Name
Type
Required
Notes
bank_id
string
Yes
Must belong to the user, from Prerequisite 1
mandate_limit
number
Yes
Maximum amount BSE can debit per transaction under this mandate
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Body
Name
Type
Required
Notes
mandate_id
string
Yes
2.3 Mandate status values
Status
Meaning
created
Mandate created; the user has not yet completed eNACH authorization
submitted
User completed eNACH authorization; BSE is processing
approved
Ready to use as the payment_source for a SIP, SWP or STP order
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.
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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
BodyCreateTransactionBasketDTO
Name
Type
Required
Notes
user_id
string
Yes
goal_id
number
No
user_goal_id
string (UUID)
No
Optional
model_portfolio_id
number
No
total_amount
number
Yes
is_redemption_full
boolean
No
is_euin
boolean
No
false suppresses the tenant's EUIN on this order
is_bulk
boolean
No
Leave false; true is for the admin bulk-upload path
consent_email
string
No
transaction_basket_items
CreateTransactionBasketItemDTO[]
Yes
See the item table below
CreateTransactionBasketItemDTO
Field
Notes
transaction_type
One of: lumpsum, sip, redemption, swp, switch_fund, stp
fund_isin
Required
folio_number
For existing-folio transactions
amount / units
amount for purchase-type, units for redemption by units
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.
{ "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.
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.
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
Question
Where to look
Notes
What's my total invested, current value and today's change?
GET /api/portfolio/dashboard
See Dashboard below.
What do I currently hold, fund by fund?
GET /api/portfolio/v2/holdings
See Holdings below.
How is my portfolio split by category, sector, market cap and stock?
GET /api/portfolio/portfolio_analysis
Computed live from current holdings. See Portfolio analysis below.
Show me a returns-over-time chart
GET /api/portfolio/returns_graph
See Returns graphs below.
Give me a downloadable capital-gains report
GET /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 above
See 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.
{ "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.
{ "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.
{ "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.
Streams the report as an Excel file with Content-Disposition: attachment.
Headers
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <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
Name
Required
Notes
tenant_id
Yes
Identifies the distributor
Authorization
Yes
Bearer <access_token>
Query parameters
Name
Type
Required
Notes
user_id
number
Yes
type
string
No
Status filter. Each route has a sensible default list of statuses if omitted
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.