Investor creation is asynchronous.
POST returns 201 immediately with
status: PENDING; registration with the exchange happens in the background.
An investor must reach status: ACTIVE before you can place an order for
them — REGISTERED is not enough: the investor must also complete the
exchange’s client authorisation (see Registration lifecycle).To save an investor without registering them yet — e.g. while a form is still
being filled in — pass
"submit": false. The investor is persisted as
status: DRAFT and nothing is sent to the exchange. Submit it later with
PATCH /api/investors/v1/:id and "submit": true (see
Retry after a rejection below — the same submit
flag applies to a DRAFT, not just a REJECTED investor).Public Onboarding Links: A
DRAFT investor can be securely shared with the end-user so they can complete the application themselves. Call POST /api/investors/v1/:id/public-link (optionally protecting it with a PIN code) to generate a public link URL. The end user hits the link, fills out the missing fields, and when they click submit, it hits the exchange and proceeds to PENDING. The same link can be generated for a FATCA_PENDING investor so they can correct their FATCA details (typically the tax identification number) themselves; on submit the profile is updated and FATCA re-submitted, and the link stops working once the investor is REGISTERED.Scope of this guide
This guide covers the two most common holding / tax-status combinations:
Joint / anyone-or-survivor holding and the other tax statuses (
MINOR,
HUF, COMPANY, TRUST) are planned and will be documented later.
Required fields by category
The exchange’s UCC registration form (NSE MF Invest, Client Common 183-column upload) asks for the same field set for all three categories below — a resident individual and an NRI single holder fill in the same shape. The only two differences aretax_status itself and the bank account type NRIs must
supply. Everything else that’s required is required for all three.
NRE/NRO holders send a second, overseas
foreign_address in addition to
address — see the note after the NRI example below. address is always the
domestic Indian address, and country is always sent to the exchange as India
regardless of category.Endpoint
201 with dto.InvestorData.
Example — resident individual, single holder
id is a short, human-quotable code —
I_<holding>_<tax status>_<6-digit sequence> (I_SI_IND_000042) — minted for
your account. It is the primary key, the foreign key every child resource
(investment account, mandate, order, payment) references, and it never
changes even if the tax status is later amended. See
friendly ID conventions.
Example — NRI single holder (NRE / NRO)
Same shape, with three differences:tax_statusisNRI_REPATRIABLE(NRE) orNRI_NON_REPATRIABLE(NRO).- At least one bank account must have
account_typeofNE(NRE) orNO(NRO) — otherwise the request is rejected withVALIDATION_FAILED(“NRI investors require an NRE (NE) or NRO (NO) account”). foreign_address(overseas address) is required in addition toaddress—line1,city,state,pincodeandcountrymust be present.countryis the FATCA country code (GET /api/masters/v1/countries, e.g.US);stateis free text. Omitting it is rejected withVALIDATION_FAILED.
address is the mandatory domestic Indian address and country is always
sent to the exchange as India. foreign_address is the NRI’s overseas address
(mandatory for NRE/NRO, ignored for other tax statuses); its country is the
FATCA country code. Address lines are truncated to 40 characters at the
exchange.Field reference
Top level
primary_holder
contact
address
foreign_address (NRE / NRO only)
Required when tax_status is NRI_REPATRIABLE or NRI_NON_REPATRIABLE, in
addition to address. Ignored for any other tax status.
Valid state values
state is free text on this API, but the exchange only accepts its own fixed
State Master list, so anything you send is normalized (uppercased, trimmed)
and looked up in the table below. Anything not in this table — including a
state’s own NSE code sent directly — silently falls back to OTHERS,
it does not error. Send the exact name from the left column (a couple of
common alternate spellings are accepted too) to avoid silently landing in
OTHERS.
The same list is served programmatically by
GET /api/masters/v1/states, and every
other coded field on this request (tax statuses, occupations, bank account
types, nominee relationships, …) has a dropdown catalog there too — see
Form Lookup Tables.
country is not actually a free choice: whatever you send, the domestic
address block is always registered with the exchange as India, since a
domestic UCC registration requires it and nothing else is accepted here.demat
bank_accounts[]
On
GET /api/investors/v1/:id, each bank_accounts[] entry additionally
carries id — the bnk_… bank_account_id you reference on orders,
payments, and mandates — and status. That list reflects accounts added or
removed after creation too (via the bank-account endpoints), not just what
was submitted at creation time.status starts ADDED and is our own — it does not reflect NSE’s own
back-office processing of that specific account, which can lag well behind
the investor’s own UCC registration. Call
POST /api/investors/v1/sync to pull NSE’s own
per-account status in.nominees[]
fatca (optional but recommended)
euin (optional)
{ "declaration": true, "euin": "E123456" } — the Employee Unique
Identification Number of the advising employee. NSE’s UCC registration has
no EUIN field, so this isn’t sent at investor creation — it’s stored as
this investor’s own default and applied to any purchase
order placed for them that
doesn’t declare its own euin (which in turn falls back to your tenant’s
default EUIN if neither is set).
Validation
Rejected synchronously with400 VALIDATION_FAILED and a details[] array:
primary_holder.name,primary_holder.dob,contact.email,contact.mobile,address.city,address.pincode— required.holding_typemust be one of the three enum values.SINGLEholding ⇒joint_holdersmust be empty.demat.modeCDSL/NSDL⇒dp_idandclient_idrequired.nomineespresent ⇒percentagesums to 100; a nominee with aguardianbut nodobis rejected.- NRI tax status ⇒ at least one
NE/NObank account.
Registration lifecycle
ThePOST returns immediately; UCC registration with the exchange (and the
optional FATCA submission that follows it) happens in the background.
- DRAFT — persisted locally,
submit: falsewas passed (on create, or on aPATCHof a not-yet-REGISTEREDinvestor) — nothing sent to the exchange yet. - PENDING — persisted locally, queued for the exchange.
- PROCESSING — the exchange call is in flight.
- FATCA_PENDING — the UCC registered but the exchange rejected the FATCA
declaration (e.g. a missing
fatca.tin). The reason is inprovider_remarkand theFATCAentry ofprovider_steps. The investor is editable:PATCHthe corrected data (the UCC is updated and FATCA re-submitted) orPOST /api/investors/v1/:id/retryto re-submit FATCA alone. It becomesREGISTEREDonce FATCA is accepted. Orders are blocked until then. - REGISTERED — UCC registered and, if FATCA was supplied, FATCA accepted,
but the investor has not yet completed the exchange’s client
authorisation (the confirmation link NSE emails/SMSes them). Orders are
blocked (
422 KYC_INCOMPLETE) until the investor isACTIVE. Why it’s still waiting is inclient_authorization(below). - ACTIVE — registered and client-authorised: the investor is ready to
transact. We poll the exchange’s Client Authorization report about every 30
minutes during exchange hours and move the investor to
ACTIVE, emittinginvestor.activated. - REJECTED — persisted (not discarded), but not terminal — correct the
data and resubmit. The exchange’s reason is in
provider_remark, with a full attempt history inprovider_steps.
Track progress
PollGET /api/investors/v1/:id:
investor.registered / investor.activated / investor.rejected.
Client authorisation (why a REGISTERED investor isn’t ACTIVE)
After the UCC is registered, the exchange sends the investor an authorisation
link, and the investor must complete it before they can transact. Until they
do, the investor stays REGISTERED and GET carries a client_authorization
object showing where it stands (it appears after the first check, roughly
30 minutes after registration at most):
To nudge a stuck investor, fetch the confirmation link (next section) and
send it to them again.
Getting the UCC confirmation link
NSE emails/SMSes the investor a confirmation link once the UCC is REGISTERED; completing it is what makes the investorACTIVE. If you’d rather show that link in your own app instead of
waiting on NSE’s email, pass ?auth_link=true on the same GET:
auth_link is fetched live from NSE on every call with the flag set — it’s
never stored. It’s only populated while status is REGISTERED (the link has
nothing left to do once the investor is ACTIVE); omitting the flag, or
fetching before registration completes, leaves it out of the response.
A REJECTED investor carries the reason in the last provider_steps entry
({ "step": "UCC", "status": "REJECTED", "remark": "..." }) and in the
top-level provider_remark.
A FATCA_PENDING investor’s FATCA rejection is in the FATCA entry of
provider_steps and in provider_remark (prefixed FATCA:). The response
of a PATCH or public-link submission that leaves the investor
FATCA_PENDING also carries provider_remark.
Retry after a rejection
If the data needs correcting:PATCH /api/investors/v1/:id with the corrected
fields (same schema) and "submit": true. While the investor is not yet
REGISTERED, PATCH only updates the stored data — it stays (or, from
REJECTED, moves back to) DRAFT unless submit: true is passed, in which
case it’s queued for the exchange again (a DRAFT registers for the first
time; a REJECTED investor is resubmitted). Once REGISTERED or ACTIVE, PATCH
always performs a synchronous UCC modification instead — submit has no
effect there.
For a FATCA_PENDING investor, PATCH with the corrected body (including
fatca.tin for NRIs) updates the UCC and re-submits FATCA; POST …/retry
re-submits only the stored FATCA declaration. The investor becomes
REGISTERED once FATCA is accepted.
If the data was fine and the rejection was transient (exchange unavailable):
POST /api/investors/v1/:id/retry — no body — re-queues the stored request
as-is. Rejected with 409 if the investor is already REGISTERED or ACTIVE.
Syncing status from NSE
status normally only changes from our own push to NSE (creation, PATCH,
retry). If a push’s synchronous response was ever lost — a timeout, a
crashed delivery — an investor can be stuck PENDING/PROCESSING locally
even though NSE actually completed the UCC registration. POST /api/investors/v1/sync — no body — reconciles every investor in your tenant
against NSE’s own Client Master report and corrects any such investor to
REGISTERED (it then becomes ACTIVE through the normal client-authorisation
check — the sync never sets ACTIVE itself, and never moves an ACTIVE
investor back).
It also reconciles bank account status: NSE tracks its own status per
bank account, separate from UCC status — an account can still show PENDING
in NSE’s back office well after the investor’s UCC itself is REGISTERED.
Each stored bank account is matched to NSE’s copy by account number + IFSC
and corrected to match:
status fields — it doesn’t touch FATCA (NSE’s Client
Master report doesn’t carry a FATCA status) or overwrite any other field.
KYC checks
An investor’s PAN must be KYC-compliant with a KYC Registration Agency (KRA) before the exchange will let them transact. There are two KYC endpoints, and both return the samekyc_status values:
In both responses the
pan is masked (first two and last two characters,
e.g. AB******4Q).
kyc_status values
The remark field (
kyc_status_remark on PAN check, remark on investor KYC
status) is the KRA’s free-text explanation. Show it to the investor, but
don’t parse it — branch on kyc_status only. In most cases it’s specific
enough to tell the investor what actually went wrong (e.g. why a KYC was put
on hold or rejected) — it comes straight from the KRA, so its wording and
level of detail vary by KRA and can’t be normalized further. It can be blank
even for a non-compliant status if the KRA didn’t supply one.
If the exchange or KRA can’t be reached, both endpoints return 502 PROVIDER_UNAVAILABLE instead of a status. It’s safe to retry. You never get
a kyc_status for a lookup that didn’t complete.
Deciding whether to start a fresh KYC
Branch onkyc_status alone — never start a fresh eKYC just because a
lookup came back non-compliant:
PAN check — GET /api/kyc/v1/pan-check
name, the three dates, kyc_status_remark and kra_name come straight
from the KRA record for the PAN. The dates are in the KRA’s own format, not
ISO 8601. kra_name is the KRA that holds the record. Any of them can be
blank.
Errors: 400 VALIDATION_FAILED if pan is missing; 502 PROVIDER_UNAVAILABLE if the lookup couldn’t be completed.
Sandbox: this endpoint doesn’t call the exchange. It returns
kyc_status: "KYC_VALIDATED" for every PAN except the reserved demo PAN
ABCDE1234F, which returns kyc_status: "KYC_NOT_FOUND". Sandbox responses
carry only pan, kyc_status and a kyc_status_remark starting with
Demo mode:. name, the dates and kra_name are absent. Production always
performs the real KRA lookup and can return any value in the table above.Investor KYC status — GET /api/investors/v1/:id/kyc-status
Errors:
404 NOT_FOUND if the investor doesn’t exist in your account;
502 PROVIDER_UNAVAILABLE if the lookup couldn’t be completed.
Starting eKYC
amc_code here is a small, fixed vocabulary specific to fresh KYC
registration — not the scheme-master amc_code from
GET /api/schemes/v1/amcs. Fetch the list your account supports first:
kyc_status is KYC_NOT_FOUND (or KYC_REJECTED and the investor has
addressed the remark) — see Deciding whether to start a fresh
KYC — start a fresh eKYC for an
existing investor using one of those codes:
{ "verification_url": "...", "message": "..." }. Send
verification_url to the investor; they finish KYC there. Afterwards, check
again until kyc_status is KYC_VALIDATED or KYC_REGISTERED.
Starting eKYC without an investor
You don’t need an investor record to start a fresh KYC — a bare PAN is enough (typically afterpan-check returned KYC_NOT_FOUND). amc_code is the same
eKYC vocabulary as above; mobile is 10 digits and email is where the
exchange sends verification.
{ "verification_url": "...", "message": "..." }, same as
POST /api/investors/v1/:id/ekyc. To get a shareable link instead, call
POST /api/kyc/v1/registration-link with the same body (plus optional
secret_code); it returns { link_id, url } and is resolved by the same
/api/public/v1/kyc-links/:link_id routes described below (the preview’s
investor_name is empty). The PAN and contact details are stored encrypted
and the link expires after 7 days. Invalid pan/mobile/email or an
unknown amc_code return 400 VALIDATION_FAILED.
Fresh KYC Registration Link
Instead of calling/ekyc yourself and forwarding verification_url, you
can generate a public link that does both steps for you — visiting it starts
eKYC and redirects straight to the exchange’s verification page:
secret_code is optional, same PIN convention as the public onboarding
links described above. Share url
directly with the investor (SMS/WhatsApp/email) — opening it in a browser
starts eKYC at the exchange and 302-redirects there immediately; append
?secret=1234 if a PIN was set. Unlike every other public link in this API,
GET .../kyc-links/:link_id/redirect returns an HTTP redirect, not JSON —
it’s designed to be the literal link the investor clicks.
To show the investor some context (their name, masked PAN, AMC code) before
sending them on — e.g. from your own PIN-gated confirmation page — call
GET /api/public/v1/kyc-links/:link_id first (PIN via the X-Draft-Secret
header, not the query string) and only then navigate the browser to the
/redirect URL above. This link expires after 7 days; generate a new one if
it lapses before the investor completes eKYC.
To list the links already generated for an investor (e.g. in your
dashboard’s investor page, KYC section), call
GET /api/investors/v1/:id/kyc-registration-links. It returns, newest first,
link_id, url, amc_code, pin_protected, status (ACTIVE or
EXPIRED), created_at and expires_at. The PIN itself is never returned.