Skip to main content
An investor is the person (or entity) who owns the mutual fund holdings. Creating one registers a Unique Client Code (UCC) with the exchange and any other backend providers enabled for your account.
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 are tax_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.
Field-by-field detail, including formats and enum values, is in Field reference below.

Endpoint

Returns 201 with dto.InvestorData.

Example — resident individual, single holder

Response:
The 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:
  1. tax_status is NRI_REPATRIABLE (NRE) or NRI_NON_REPATRIABLE (NRO).
  2. At least one bank account must have account_type of NE (NRE) or NO (NRO) — otherwise the request is rejected with VALIDATION_FAILED (“NRI investors require an NRE (NE) or NRO (NO) account”).
  3. foreign_address (overseas address) is required in addition to address — line1, city, state, pincode and country must be present. country is the FATCA country code (GET /api/masters/v1/countries, e.g. US); state is free text. Omitting it is rejected with VALIDATION_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.
This list is transcribed from the exchange’s own State Master and has known gaps — notably Ladakh has no entry and falls back to OTHERS. It has not been reconfirmed as current with the exchange. If an investor’s registration lands in an unexpected bucket for their state, that’s the likely cause — contact support rather than assuming it’s an input error on your end.
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[]

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 with 400 VALIDATION_FAILED and a details[] array:
  • primary_holder.name, primary_holder.dob, contact.email, contact.mobile, address.city, address.pincode — required.
  • holding_type must be one of the three enum values.
  • SINGLE holding ⇒ joint_holders must be empty.
  • demat.mode CDSL/NSDL ⇒ dp_id and client_id required.
  • nominees present ⇒ percentage sums to 100; a nominee with a guardian but no dob is rejected.
  • NRI tax status ⇒ at least one NE/NO bank account.

Registration lifecycle

The POST returns immediately; UCC registration with the exchange (and the optional FATCA submission that follows it) happens in the background.
  • DRAFT — persisted locally, submit: false was passed (on create, or on a PATCH of a not-yet-REGISTERED investor) — 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 in provider_remark and the FATCA entry of provider_steps. The investor is editable: PATCH the corrected data (the UCC is updated and FATCA re-submitted) or POST /api/investors/v1/:id/retry to re-submit FATCA alone. It becomes REGISTERED once 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 is ACTIVE. Why it’s still waiting is in client_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, emitting investor.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 in provider_steps.

Track progress

Poll GET /api/investors/v1/:id:
GET always returns PII masked. See PII masking for how to reveal a single field.
Or listen for webhook events 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. NSE emails/SMSes the investor a confirmation link once the UCC is REGISTERED; completing it is what makes the investor ACTIVE. 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:
This only corrects 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 same kyc_status values: In both responses the pan is masked (first two and last two characters, e.g. AB******4Q).

kyc_status values

Treat only KYC_VALIDATED and KYC_REGISTERED as compliant. Handle unrecognised values the same way as KYC_UNKNOWN, since new statuses may be added to this list.
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 on kyc_status alone — never start a fresh eKYC just because a lookup came back non-compliant:
Only KYC_NOT_FOUND and, after the investor has acted on the remark, KYC_REJECTED should ever lead to POST .../ekyc. Anything else means wait and poll, not resubmit.

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:
When 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:
The response is { "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 after pan-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.
The response is { "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. 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.