A KYC API for fintech and NBFC onboarding in India is a set of server-to-server checks that confirm a borrower's identity and business documents against registry data while the application is still open. With StampMitra you call one endpoint per document (PAN, bank account, vehicle RC, driving licence, voter ID, passport, GSTIN, CIN, Udyam) and get a structured JSON result plus a referenceId you can store against the applicant. You sequence those calls so the cheapest and most decisive checks run first.
One point has to be clear before any code: a successful API check is evidence you attach to a file. It is not, by itself, regulatory KYC compliance. A regulated entity such as an NBFC must follow its own regulator's KYC rules, which govern who may be onboarded, how identity is established, what is recorded and how often it is refreshed. This guide covers the engineering side: journeys, ordering, cost, retries, review queues, logging and async design.
Everything below is grounded in the verification endpoints documented in the StampMitra verification API reference. Where the platform does not offer something today (for example, self-serve webhooks for verification results), the guide says so and shows the pattern you build on your side instead.
Key takeaways
- Each check is a synchronous
POST /v1/verify/<product>call authenticated with an API key (Authorization: Bearer <key>orx-api-key), scoped per product, for exampleverification.pan.read. - Validate formats in your own code first. Malformed input fails with a 400, but wasted calls and slow journeys still cost you.
- Both a positive and a negative determination (
valid: false,found: false) are completed, billable verifications, so retrying a "no" blindly just pays twice. - Run cheap, high-signal checks (PAN, then bank account) before expensive or conditional ones, and stop the journey early when a hard failure appears.
- Separate three outcomes: pass, hard fail, and "needs a human". Route the third to a manual-review queue with the evidence attached.
- Store the
referenceId, the response body you relied on, the timestamp and your decision. Keep personal data only as long as your purpose and your legal duties require. - An API result does not replace your regulator's KYC requirements. For NBFCs the reference is the RBI Know Your Customer (KYC) Direction, 2016, as updated. Ask your compliance team how each API check maps to your policy.
- Verification endpoints are synchronous today. The webhooks page documents order-lifecycle events, so build your own queue for async verification work.
What does a KYC API actually do in a lending onboarding flow?
A lending onboarding flow collects claims from the applicant: "my PAN is this", "this is my bank account", "this company is mine". A verification API tests each claim against a registry-backed source and returns a normalised answer. It does not decide whether to lend, and it does not decide whether your KYC obligations are met.
The unit of work: one document, one call
Every StampMitra verification product follows the same shape. You send a JSON body with the document number and optional matching fields, you get back a JSON object. The PAN endpoint, for example, is POST https://api-b2b.stampmitra.in/v1/verify/pan with pan required and name optional. The response carries fields such as valid, registeredName, panStatus, nameMatchResult and nameMatchScore, which your rules engine can consume directly. See the PAN verification guide for a deeper walkthrough of that single endpoint.
Where it sits in your stack
The API is called from your backend, never from a browser or mobile app, because the key carries scopes that spend money. A typical lending stack has an application service, a rules or decision engine, a document store and a case-management tool for analysts. The verification calls sit between the application service and the decision engine: they enrich the application with checked facts.
What it does not do
It does not capture consent, hold the applicant's documents, run liveness or video interviews, or file anything with a regulator. Those remain your responsibilities or those of other vendors. Keeping this boundary in view prevents the most common design error in KYC projects, which is treating "the API said valid" as "the customer is KYC-complete".
Which checks apply to individual and to business borrowers?
The checks you pick depend on borrower type and on the product being sold. The table maps the nine verification products named in this guide, with the scope each call needs and the main fact it returns. Two helper endpoints, pan-to-gstin and pan-to-udyam, resolve a PAN to registrations and are covered in the business journey below.
| Product slug | Endpoint | Scope | Key input | What you learn |
|---|---|---|---|---|
| pan | /v1/verify/pan | verification.pan.read | pan, optional name | Validity, status, registered name, name-match result |
| bank-account | /v1/verify/bank-account | verification.bank-account.read | accountNumber, ifsc, optional name | Account exists, beneficiary name, name match |
| rc | /v1/verify/rc | verification.rc.read | vehicleNumber | Registration status, owner name, class, insurance date |
| driving-license | /v1/verify/driving-license | verification.dl.read | dlNumber, dob | Licence validity, name, status |
| voter-id | /v1/verify/voter-id | verification.voter.read | voterId, optional name | Validity, name, date of birth |
| passport | /v1/verify/passport | verification.passport.read | fileNumber, dob, optional name | Validity, name, application type |
| gstin | /v1/verify/gstin | verification.gstin.read | gstin, optional businessName | Registration status, legal and trade name |
| cin | /v1/verify/cin | verification.cin.read | cin | Company status, incorporation date, directors |
| udyam | /v1/verify/udyam | verification.udyam.read | udyamNumber | Registration found, status, enterprise type |
Individual borrowers
For a salaried or self-employed individual the usual evidence set is a PAN check with name match, a bank-account check with name match for the disbursal or repayment account, and one more identity document where your policy asks for it (driving licence, voter ID or passport). For vehicle-backed loans the RC check ties the asset to the borrower.
Business borrowers
For a proprietorship, partnership or company, you verify the entity and the people behind it: GSTIN for tax registration, CIN for a company's existence and directors, Udyam for MSME status, and the entity's own bank account. A deeper treatment lives in the business verification guide.
A note on what each field means
A field such as registrationStatus: "Active" on a GSTIN response reports what the source returned at call time. It says nothing about the borrower's creditworthiness, and it can change tomorrow. Record the timestamp of every call so a later reviewer knows how fresh the evidence was.
What order should you run the checks in?
Order matters for cost, speed and fraud signal. The principle is simple: run the checks that are cheapest, fastest and most likely to eliminate bad applications first, and let their results decide whether the expensive or conditional checks run at all.
The individual borrower sequence
- Local format validation (free). Check PAN against
ABCDE1234F, IFSC as 11 characters, account number as 9 to 18 digits, DL and voter ID against the formats in the docs. The API validates input format up front and fails fast with a 400, but catching a typo in your form gives the applicant instant feedback and avoids a round trip. - PAN with name. It anchors the identity. A
valid: false, a non-validpanStatus, or a poornameMatchResultusually ends the journey or sends it to review. - Bank account with name. Run it only after PAN passes, and compare
beneficiaryNameandnameMatchagainst the PAN'sregisteredName. Disagreement between the two registries is a strong fraud signal. - Secondary identity document (driving licence, voter ID or passport) only when your policy requires it or when the PAN result is ambiguous.
- RC only for vehicle-backed products, once the person is otherwise accepted.
The business borrower sequence
- Format checks on GSTIN, CIN or Udyam number, whichever the applicant supplied.
- The primary registry for the entity type: CIN for companies, GSTIN for GST-registered firms, Udyam for micro and small enterprises.
- The authorised signatory's or proprietor's individual journey (PAN, then bank).
pan-to-gstinorpan-to-udyamto cross-check that the registrations you were given belong to the PAN you verified.- The entity's bank account.
Why "cheapest first" is not the whole rule
A cheap check that rarely discriminates (for instance, a driving licence for someone who already passed PAN and bank match) adds latency and cost without changing decisions. Order by expected information gain per rupee, not by price alone. Our business verification guide and the individual endpoint pages show which fields give you the discriminating signal for each product.
How do you control verification cost across a funnel?
Verification spend is a funnel-design problem. Most applications drop off before disbursal, so you want to spend little on the many and more on the few who reach underwriting. Prices are per call and metered after a successful response; the live per-call price for each product is on the pricing page and in the product data.
Current per-call prices
The figures below are resolved from StampMitra's product data when this page is built, so they stay in step with the price list.
| Check | Price per call |
|---|---|
| PAN | ₹5 |
| Bank account | ₹4 |
| GSTIN | ₹5 |
| CIN | ₹5 |
| Udyam | ₹5 |
| Vehicle RC | ₹9 |
| Driving licence | ₹5 |
| Voter ID | ₹5 |
| Passport | ₹9 |
| PAN to GSTIN | ₹5 |
| PAN to Udyam | ₹5 |
Staged spending
Divide the journey into stages and attach a budget to each. Stage one (lead capture) spends nothing. Stage two (soft application) runs PAN only, which at ₹5 per call is the cheapest decisive check. Stage three (underwriting) runs the bank account at ₹4 and any business registry calls. Stage four (pre-disbursal) re-checks only what must be fresh.
Billing rules that shape your logic
The product docs state that usage is recorded, and the wallet debited for priced services, only after a successful response. The same notes state that negative determinations (valid: false, found: false) are completed and billable. Two consequences follow. First, never loop a "not found" result hoping it flips; a retry buys another answer at full price. Second, a 402 PAYMENT_REQUIRED (insufficient wallet balance) is a platform state you should alert on before it stops a whole funnel.
Avoiding duplicate spend
Cache the result against a normalised key (for example the PAN) inside your own system for the duration your policy allows, and check the cache before calling. Deduplicate concurrent submissions from the same applicant at your gateway. The verify endpoints do not document an idempotency header, and the referenceId is StampMitra's transaction id, not an idempotency key, so deduplication is your job. The sandbox versus production keys guide explains how to test this logic without spending real money.
How do you build the checks into a working onboarding service?
This section walks through a minimal but realistic service. It uses the documented PAN and bank-account endpoints and shows how to turn raw responses into three outcomes.
Step 1: get a key and scopes
Sign up for a sandbox key (prefix sk_test_) through signup; production keys (sk_live_) require a KYC-approved organisation, as described in the authentication docs. Create separate keys per service so each carries only the scopes it needs: verification.pan.read, verification.bank-account.read, and so on. A key missing a scope gets a 403 FORBIDDEN.
Step 2: call PAN with the name the applicant typed
curl -X POST "https://api-b2b.stampmitra.in/v1/verify/pan" \
-H "Authorization: Bearer $STAMPMITRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pan":"ABCDE1234F","name":"Rahul Kumar"}'The documented response shape (sample values) is:
{
"referenceId": "SMV-20260927-8F42K7",
"pan": "ABCDE1234F",
"type": "Individual",
"valid": true,
"registeredName": "RAHUL KUMAR",
"fatherName": "SURESH KUMAR",
"panStatus": "VALID",
"panStatusCode": "E",
"panStatusDescription": "Existing and valid PAN",
"nameMatchResult": "MATCH",
"nameMatchScore": "100",
"aadhaarSeedingStatus": "y",
"aadhaarSeedingStatusDescription": "Aadhaar linked",
"lastUpdatedAt": "2023-04-01",
"message": "PAN is Valid"
}Step 3: the Node.js version
// Node 18+ (global fetch). Runs on your server only.
const BASE = "https://api-b2b.stampmitra.in";
async function verify(path, body) {
const res = await fetch(`${BASE}/v1/verify/${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STAMPMITRA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const json = await res.json();
if (!res.ok) {
const err = new Error(json.error?.message ?? "verification failed");
err.status = res.status;
err.code = json.error?.code;
err.requestId = json.error?.requestId;
throw err;
}
return json;
}
async function checkIndividual(app) {
const pan = await verify("pan", { pan: app.pan, name: app.fullName });
if (!pan.valid) return { outcome: "HARD_FAIL", reason: "PAN_INVALID", evidence: [pan] };
if (pan.nameMatchResult !== "MATCH") {
return { outcome: "REVIEW", reason: "PAN_NAME_MISMATCH", evidence: [pan] };
}
const bank = await verify("bank-account", {
accountNumber: app.accountNumber, ifsc: app.ifsc, name: app.fullName,
});
if (!bank.accountExists) return { outcome: "HARD_FAIL", reason: "ACCOUNT_NOT_FOUND", evidence: [pan, bank] };
if (!bank.nameMatch) return { outcome: "REVIEW", reason: "BANK_NAME_MISMATCH", evidence: [pan, bank] };
return { outcome: "PASS", evidence: [pan, bank] };
}Step 4: the Python version
import os, requests
BASE = "https://api-b2b.stampmitra.in"
class VerifyError(Exception):
def __init__(self, status, code, message, request_id):
super().__init__(message)
self.status, self.code, self.request_id = status, code, request_id
def verify(path, body, timeout=20):
r = requests.post(
f"{BASE}/v1/verify/{path}",
headers={"Authorization": f"Bearer {os.environ['STAMPMITRA_API_KEY']}"},
json=body, timeout=timeout,
)
data = r.json()
if not r.ok:
e = data.get("error", {})
raise VerifyError(r.status_code, e.get("code"), e.get("message"), e.get("requestId"))
return data
pan = verify("pan", {"pan": "ABCDE1234F", "name": "Rahul Kumar"})
print(pan["referenceId"], pan["valid"], pan["nameMatchResult"])The PAN in Node.js and Python guide goes further on client structure, and the SDKs page lists what is available today.
Step 5: treat the match fields as inputs, not verdicts
nameMatchResult and nameMatchScore are signals. Your policy decides what counts as a pass. Many teams accept an exact match, send partial matches to review, and reject low scores, but the thresholds belong in your risk policy, not in this guide. Note also that nameMatchScore on the PAN sample is a string and on the bank-account sample a number (0.95), so parse each defensively.
How should a business borrower journey be verified?
Business onboarding needs two separate questions answered: does this entity exist and is it active, and does this person have a real connection to it. The registry endpoints answer the first. The second needs your own evidence, such as board resolutions or authorisation letters, which no verification API replaces.
Company (Pvt Ltd or LLP-style entity with a CIN)
Call cin with the Corporate Identification Number. The documented response includes companyName, incorporationDate, cinStatus, companyStatus and a directors array with din, name and designation. Compare companyName with the name on the application and with the GSTIN legalName. Compare each director's name with the individuals who will sign. Run an individual journey (PAN, bank) for each signatory your policy requires. The response does not include directors' PAN, so do not expect to derive it from the CIN.
curl -X POST "https://api-b2b.stampmitra.in/v1/verify/cin" \
-H "Authorization: Bearer $STAMPMITRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cin":"U74999DL2015PTC284009"}'GST-registered firm
Call gstin with the 15-character number and, optionally, businessName. Documented response fields include GSTIN, valid, legalName, tradeName, registrationStatus and principalPlaceOfBusiness. Note the capitalised GSTIN key in the response, while the request uses lowercase gstin. Registration status is a point-in-time fact; a cancelled or suspended registration is a review trigger.
MSME with Udyam
Call udyam with the registration number. The response has found, udyamStatus, enterpriseName, enterpriseType, organizationType and a certificateUrl. Do not store the certificate URL as proof: store the response and the referenceId.
Cross-checking with PAN
pan-to-gstin returns a gstins array (each with gstin, state, status) for a PAN, and pan-to-udyam returns found, status and udyamNumber. These are useful consistency checks: if an applicant presents a GSTIN that is not among those linked to the verified PAN, that is worth a reviewer's attention. Their prices are ₹5 and ₹5 per call. The GSTIN verification guide covers the registration fields in more depth.
Proprietorships
A proprietor and the business are one legal person, so the PAN is the anchor. Verify the proprietor's PAN and bank account, then use pan-to-gstin and pan-to-udyam to discover and confirm the business registrations rather than relying only on what the applicant types.
How do you handle retries, errors and the manual-review queue?
Reliability design in KYC is mostly about distinguishing three kinds of failure: the applicant gave bad data, the source could not answer, and the answer is ambiguous. Each has a different remedy.
Error codes you will see
Every error is normalised to { "error": { "code", "message", "requestId" } }. The documented cases for verify endpoints are in this table.
| HTTP | code | Meaning | Retry? | Typical action |
|---|---|---|---|---|
| 400 | BAD_REQUEST | Input failed format validation | No | Ask the applicant to correct the field |
| 401 | UNAUTHORIZED | Missing or invalid API key | No | Fix configuration; page an engineer |
| 402 | PAYMENT_REQUIRED | Insufficient wallet balance | Only after top-up | Alert finance; queue work |
| 403 | FORBIDDEN | Key lacks the scope | No | Fix key scopes |
| 429 | TOO_MANY_REQUESTS | Rate limited | Yes, with backoff | Slow down, spread calls |
| 502 | INTERNAL_ERROR | Upstream verification service unreachable | Yes, bounded | Retry, then park for review |
The documented 429 example message reads "Rate limit exceeded: 60 requests per 60 seconds for this endpoint". Treat that as an illustration of the error body and check the live limits for your account rather than hard-coding 60. The API errors guide expands on each code.
A bounded retry policy
Retry only 429 and 502, with exponential backoff and jitter, and cap attempts (three is a common choice). Because a successful response is billed whether positive or negative, you only pay for answers, but retrying a timeout can create ambiguity: if your client timed out after the server responded, a retry may bill again. Log the requestId from errors and the referenceId from successes so support can reconcile.
async function withRetry(fn, { tries = 3, base = 500 } = {}) {
for (let i = 0; ; i++) {
try { return await fn(); }
catch (e) {
const retryable = e.status === 429 || e.status === 502;
if (!retryable || i >= tries - 1) throw e;
const wait = base * 2 ** i + Math.random() * 250;
await new Promise(r => setTimeout(r, wait));
}
}
}What goes to manual review
Create a review case, not a rejection, when: the PAN name match is partial; PAN and bank beneficiary names disagree; a registry returns an inactive or cancelled status; an upstream error persists after retries; or a business registration does not link to the verified PAN. Each case should hold the application id, the reason code, the full evidence list with referenceIds and timestamps, and a due date.
Designing the reviewer's screen
Show the applicant's typed values beside the registry values, highlight mismatches, and offer only a small set of actions: approve with note, request more documents, reject with reason. Record the analyst's identity and time for every action. A reviewer's override is a decision your organisation owns; the API result is merely the evidence they looked at.
How do you do async processing and webhooks for verification?
Verification calls on StampMitra are synchronous request and response. You send a request, you wait for the JSON, you act. There is no documented polling endpoint for verification results and no documented webhook for them. The webhooks page describes order-lifecycle events (such as order.fulfilment_completed) with HMAC-SHA256 signing and exponential-backoff retries, and states that self-serve webhook registration is still "coming soon". Do not design a KYC flow that depends on a verification webhook today.
Make your own flow asynchronous
Even with synchronous calls, you often want the applicant's browser to return quickly. The standard pattern is: the front end submits the application, your API stores it with status PENDING and enqueues a job, a worker runs the verification sequence, and the front end polls your status endpoint or receives your own push or webhook.
| Component | Responsibility |
|---|---|
| Application API | Validates formats, stores the application, enqueues a job, returns 202 and an application id |
| Queue (SQS, Pub/Sub, Redis streams or similar) | Buffers jobs, enforces concurrency limits to respect rate limits |
| Verification worker | Runs the ordered checks, applies retries, writes evidence |
| Decision service | Maps evidence to PASS, HARD_FAIL or REVIEW |
| Case tool | Holds REVIEW items for analysts |
| Notifier | Tells the applicant or your CRM about status changes |
Concurrency and rate limits
The queue is where you enforce a global ceiling on in-flight verification calls so a campaign spike does not trigger 429s. Partition by endpoint if limits are per endpoint, as the 429 example message suggests.
If you later receive order events
For flows that do use StampMitra orders (for example e-stamping loan agreements after approval), webhook payloads carry orderId, status and occurredAt. Verify the signature, check the timestamp, respond 2xx quickly, and make your handler idempotent because deliveries can be retried. See the e-stamp order flow guide for the order side.
What does a worked example look like from request to decision?
Consider an invented applicant, Rahul Kumar, applying to a fictional lender, Kaveri Finserve, for a small personal loan. The values below are the documentation's sample values, not real data.
The bank-account step
After PAN passes, the worker calls the bank-account endpoint with the same name the applicant typed.
curl -X POST "https://api-b2b.stampmitra.in/v1/verify/bank-account" \
-H "Authorization: Bearer $STAMPMITRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accountNumber":"000123456789","ifsc":"ICIC0001234","name":"Ravi Kumar"}'The documented response shape is:
{
"accountNumber": "000123456789",
"ifsc": "ICIC0001234",
"bankName": "ICICI Bank",
"branchName": "Andheri West",
"accountExists": true,
"beneficiaryName": "RAVI KUMAR",
"nameMatch": true,
"nameMatchScore": 0.95,
"accountStatus": "ACTIVE"
}Reading the two results together
In the PAN sample the registered name is RAHUL KUMAR, while the bank sample's beneficiary is RAVI KUMAR. Those are different documentation samples, but they make a useful teaching case: had one applicant produced both, the bank step might still report nameMatch: true against the typed name "Ravi Kumar" while the PAN says Rahul. Matching each registry against the typed name is not enough. Your decision service should also compare the registry names with each other, and the disagreement should create a review case with reason PAN_BANK_NAME_DIFF.
The decision record
The worker writes one evidence row per call (PAN with its referenceId, bank result with the timestamp and your own correlation id) and the decision service writes a single decision row.
{
"applicationId": "app_01",
"outcome": "REVIEW",
"ruleVersion": "2026-10-a",
"reasons": ["PAN_BANK_NAME_DIFF"],
"evidence": [
{ "product": "pan", "referenceId": "SMV-20260927-8F42K7" },
{ "product": "bank-account", "calledAt": "2026-10-16T09:12:44Z" }
]
}The analyst opens the case, sees both names, asks the applicant for an explanation or a bank statement through your own channel, and records the outcome. Notice what the API contributed (two structured facts with timestamps) and what it did not (the judgement that these two names refer to the same person).
A business variant
For a company applicant the same shape applies: the worker calls cin, compares companyName and directors with the application, then runs PAN and bank journeys for the signatory. If cinStatus or companyStatus is anything other than the active values your policy accepts, the outcome is REVIEW with reason ENTITY_NOT_ACTIVE. If it is active and names align, the decision is PASS for this evidence set, which then feeds your wider policy checks.
What should an architecture for KYC verification look like?
Keep the architecture boring: few components, clear data ownership, and one place where decisions are written. The table below describes the text diagram flow from left to right.
| Layer | Component | Reads from | Writes to |
|---|---|---|---|
| Edge | Application form / mobile app | Applicant | Your gateway (HTTPS) |
| Gateway | Application API with auth and rate limiting | Form payload | Application DB, queue |
| Core | Verification worker | Queue, key vault | Evidence store, decision input |
| External | StampMitra /v1/verify/* | Worker request | Worker response |
| Core | Decision service | Evidence store | Decision log, case tool |
| Ops | Case-management tool | Decision log | Analyst actions log |
| Governance | Audit log and retention job | All of the above | Archive or deletion |
Secrets and keys
The API key lives in a secrets manager and is injected into the worker only. Use a sandbox key in every non-production environment. Rotate keys on a schedule and on staff changes, and restrict sensitive scopes to an IP allowlist where your plan supports it (the auth docs note sensitive scopes can be IP-restricted). The platform's API security policy sets out the provider side of the shared responsibility.
Data model sketch
Keep one verification_check row per call: application id, product slug, request fingerprint (a hash, not the raw number, where possible), referenceId, HTTP status, outcome fields you used, the full response stored in an encrypted column, called_at, and requestId for errors. One decision row per outcome: result, rule version, reasons, actor (system or analyst). This lets you replay a decision under the rule version in force at that time.
Isolating personal data
Put raw identifiers in a restricted store with its own access controls, and let downstream analytics see only tokens. A bug in a dashboard should never be able to leak a PAN.
What audit logging and data retention practices make sense?
Logs are how you prove, months later, what was checked and why a decision was taken. They are also a liability, because they hold personal data. Good practice balances both, and your own legal and compliance advisers decide the actual periods.
What to log
Log who initiated the check (system or user id), the product called, the referenceId, the timestamp, the HTTP status, your decision and the rule version. For errors log the requestId from the error body, which StampMitra support can use to trace the call. Log reviewer actions with identity and time. Do not log full identity numbers in application logs, and never log API keys.
Make audit records tamper-evident
Write decision and reviewer events append-only, with a hash chain or write-once storage, and restrict deletion to a documented retention job. If an auditor asks who changed a decision, the system should answer without relying on anyone's memory.
Retention: the general principle
The DPDP Act 2023 (Act No. 22 of 2023) is built around consent and purpose limitation, and it provides that personal data should be erased once the purpose is served or consent is withdrawn, unless law requires retention (sections 6 and 8 as published; confirm the current text and any rules made under it with counsel). For a lender, the "unless law requires retention" clause matters, because sector rules may require you to keep KYC records for fixed periods. Your retention schedule should therefore be set per record type with a legal basis noted for each, not as one global number.
What StampMitra retains and what you should read
StampMitra's own approach to retaining information processed through the platform, including API logs, request identifiers and verification requests and results, is described in the developer data retention and deletion schedule, which states that retention depends on purpose, law and contract rather than one fixed number of days. Roles and responsibilities when StampMitra processes personal data for you are in the Data Processing Addendum and the developer privacy policy. Read them with your counsel; they are the authoritative statements, not this summary.
Consent capture
Before calling a verification endpoint on a person's data, record what the person was told, what they agreed to, when, and through which screen version. Collect only the fields the check needs. This is a good practice summary, not legal advice.
What does an API check NOT satisfy?
This is the section to show your product manager. A StampMitra verification call confirms that a document number returns a result from a data source and, where you pass a name, how it compares. That is useful evidence. It is not any of the following.
Not regulatory KYC compliance
Regulated entities must follow the KYC rules of their own regulator. For NBFCs, banks and other entities regulated by the Reserve Bank of India, the governing instrument is the Master Direction, Know Your Customer (KYC) Direction, 2016 (RBI/DBR/2015-16/18), issued on 25 February 2016 and updated since; the RBI page showed an update as on 14 August 2025 when we checked on 16 October 2026. It applies to every entity regulated by the RBI, including NBFCs, and covers customer acceptance, risk management, customer identification and transaction monitoring, among other things. Its text, including permitted identification methods, record-keeping and periodic updation, is what you must follow. Entities regulated by SEBI, IRDAI or others have their own rules.
Not a substitute for your KYC policy and officers
The Direction expects a board-approved policy and designated officers inside the regulated entity. An API cannot hold those roles. Whether a particular API result may be used as part of your identification process is a question for your compliance function and, where needed, your legal counsel, and may depend on the exact document and method.
Not video or in-person verification
The RBI Direction describes a video-based customer identification process (V-CIP) with its own minimum standards. StampMitra's verification endpoints do not perform live interviews, liveness checks or face matching.
Not a registry of last resort
A "found" result reflects what the source returned at call time. It does not prove the person in front of you is the person the document describes, it does not establish beneficial ownership, and it does not screen for sanctions, adverse media or politically exposed persons. Those need other controls.
Not a credit or fraud decision
Nothing here predicts repayment, and none of the checks should be the sole basis of an adverse decision against an applicant. Keep a human route for people whose data is wrong at source.
StampMitra is a private LegalTech platform operated by Bani Global Industries LLP. It is not a government agency, a regulator, or a law firm, and nothing on this page is legal advice.
When should you use this approach, and when not?
Use the API-driven pattern where speed and consistency matter and where the checks are evidence inside a larger, policy-governed process. Avoid it where the stakes or the rules demand something else.
| Situation | Fit | Why |
|---|---|---|
| Pre-screening a loan application before human review | Good | Cheap early signals cut wasted analyst time |
| Verifying a payout account before the first disbursal | Good | Bank-account and name match catch typos and mismatches |
| Business onboarding with GSTIN, CIN or Udyam | Good | Registry facts are structured and quick to compare |
| Periodic re-checks of existing borrowers | Good, with care | Re-run only what must be fresh; mind cost and consent |
| Your only evidence of identity for a regulated onboarding | Not enough | Follow your regulator's rules on identification |
| Deciding beneficial ownership of a complex group | Not enough | Needs documents and judgement beyond a registry lookup |
| Fully automated rejection with no appeal route | Avoid | Source data can be wrong; keep a human path |
| Calls directly from a mobile app | Avoid | Keys must stay on your server |
A short decision checklist
Ask whether the check is decisive for your funnel stage, whether you hold consent for it, whether a cached recent result already exists, and whether a failure has a defined next step. If any answer is no, fix that before calling.
What are the most common mistakes and failure causes?
Most production problems in KYC integrations repeat across teams. Here are the ones to design out early.
Treating "valid" as "KYC done"
Teams wire valid: true straight to an "approved" flag. Instead map it to "evidence satisfied" and let a policy engine decide, with the regulator's rules encoded in policy, not in a boolean.
Retrying negative answers
A valid: false is a completed, billable answer. Looping it burns money and leaves you with several conflicting results. Retry only transport-level problems (429, 502, timeouts).
Case and formatting mismatches
Applicants type lowercase PANs, spaces in IFSC codes, or hyphen variations in Udyam numbers. Normalise before sending: trim, upper-case, strip internal spaces. A 400 such as "pan must match the format ABCDE1234F" is the API telling you the input never reached verification.
Ignoring the response-shape differences
GSTIN returns GSTIN in capitals; the bank-account sample has no referenceId in its documented response while other products do; scores arrive as string in one product and number in another. Write one adapter per product and test it against the documented sample, rather than one generic parser.
Storing too much
Saving whole documents, full responses in logs and raw numbers in analytics tables multiplies breach impact. Store what the decision needs, encrypted, with a retention date.
No handling for wallet exhaustion
A 402 at peak hour stops every application. Monitor balance, alert at a threshold, and park jobs for retry after top-up rather than failing applicants.
Shipping sandbox assumptions to production
Sandbox keys test integration against sandbox data; production keys need an approved organisation. Test failure paths (400, 402, 429, 502) in sandbox by faking responses in your own test doubles, and read the sandbox versus production guide before go-live.
How do you test in sandbox and go to production?
Plan testing in three layers: contract tests, journey tests and failure drills. Sandbox access is free to obtain through self-signup, and the home page describes sandbox pricing as free test-mode calls against sandbox data. Which exact documents behave in which way in sandbox is documented in the product pages and may change, so read them rather than assuming.
Contract tests
For each product, send the sample request from the docs and assert the response has the fields your adapter reads. Pin those samples in your repository, and rerun on a schedule so a change to a response shape shows up in your tests before it shows up in customers' journeys.
Journey tests
Script the end-to-end flows: clean individual pass; PAN name mismatch to review; bank account not found to hard fail; company with inactive status to review; upstream error to retry then review. Assert not only outcomes but the evidence stored and the audit entries written.
Failure drills
Use test doubles to inject 401, 402, 403, 429 and 502 responses and confirm each path behaves as the table above prescribes. Kill the worker mid-job and confirm the job resumes without double-billing your own decisions.
Go-live checklist
- Production organisation approved and
sk_live_keys created with minimum scopes. - Keys in a secrets manager, rotation documented, IP restrictions applied where available.
- Wallet funded, low-balance alert configured.
- Rate-limit ceiling configured in the queue.
- Retention job and deletion procedure tested against the retention schedule and your own policy.
- Compliance sign-off that the evidence use fits your regulator's rules.
- Runbook for support: who to contact at [email protected], what identifiers to quote (
requestId,referenceId).
How do you troubleshoot problems in production?
Work from the outside in: is it your input, your credentials, your limits, or the upstream source?
Symptom table
| Symptom | Likely cause | Check |
|---|---|---|
| Repeated 400 on one field | Format not normalised | Compare with the format in the product docs; trim and upper-case |
| 401 after a deploy | Wrong or missing key in the environment | Confirm the header Authorization: Bearer <key> and that the key matches the environment (sk_test_ vs sk_live_) |
| 403 on a new product | Key lacks the scope | Add the scope such as verification.cin.read to the key |
| 402 mid-campaign | Wallet depleted | Top up; replay parked jobs |
| 429 bursts | Concurrency above the endpoint limit | Lower worker concurrency; add jitter |
| 502 spikes | Upstream source unreachable | Backoff, then route to review; keep requestId |
Unexpected valid: false | Genuine negative, or applicant typo | Re-ask the applicant once; do not loop calls |
| Name match low for correct person | Spelling, initials or transliteration differences | Send to review; do not auto-reject |
What to send to support
When you need help, email [email protected] or use the support page, and include the requestId from the error body, the endpoint, the UTC timestamp and the key's environment. Never include full PAN or account numbers in an email; the sample values in the documentation suffice to reproduce shape problems.
Observability
Track per-product call counts, error rates by code, latency percentiles, review-queue size and age, and wallet balance. A sudden rise in the review rate for one product usually means a source-side change or a form bug, and you want to see it within hours.
How StampMitra Helps
StampMitra provides the verification layer described above through one consistent API. Today the verification products available are PAN, bank account, vehicle RC, driving licence, voter ID, passport, GSTIN, CIN and Udyam, plus two resolvers that start from a PAN: PAN to GSTIN and PAN to Udyam. Each is a POST /v1/verify/<product> call with a per-product scope, one authentication scheme, a normalised error body with a requestId, and a referenceId on documented verification responses so you can tie a result to a file. Per-call prices are listed on the pricing page and metered only after a successful response.
You can start without a sales call. Sign up for a sandbox key, follow the quickstart, then read the product page for each check you plan to use, such as bank account or CIN. Sandbox keys begin with sk_test_, and moving to production keys involves a KYC-approved organisation. If you build with an AI coding assistant, the build with AI page, the MCP page and the Postman collection page give you ready starting points.
It is worth being direct about the limits. StampMitra is a private LegalTech platform operated by Bani Global Industries LLP, not a government agency, regulator or law firm. The API gives you checked data points. It does not decide your KYC compliance, run video identification, screen sanctions lists, or take on your regulatory duties, and it does not currently offer webhooks for verification results or self-serve webhook registration. Your compliance team remains the owner of how these data points are used in your KYC policy.
On the roadmap, as plans rather than promises: self-serve webhook endpoint registration, more developer tooling around testing, and wider coverage of related checks. Timelines are not committed, so build on what is documented today. For commercial or enterprise terms, use the contact page. Companion reading: the PAN verification guide, the business verification guide and the error handling guide. Terms for acceptable use are in the API acceptable use policy, and the developer onboarding verification policy explains how production access is approved.
API reference at a glance
POST /v1/verify/panRequest parameters
| Field | Type | Description |
|---|---|---|
Authorizationrequired | header | Bearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted. |
panrequired | string | PAN, format ABCDE1234F. |
name | string | Name to match against records (max 256 chars). |
POST /v1/verify/pan{
"referenceId": "SMV-20260927-8F42K7",
"pan": "ABCDE1234F",
"type": "Individual",
"valid": true,
"registeredName": "RAHUL KUMAR",
"fatherName": "SURESH KUMAR",
"panStatus": "VALID",
"panStatusCode": "E",
"panStatusDescription": "Existing and valid PAN",
"nameMatchResult": "MATCH",
"nameMatchScore": "100",
"aadhaarSeedingStatus": "y",
"aadhaarSeedingStatusDescription": "Aadhaar linked",
"lastUpdatedAt": "2023-04-01",
"message": "PAN is Valid"
}POST /v1/verify/panPAN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).
POST /v1/verify/bank-accountRequest parameters
| Field | Type | Description |
|---|---|---|
Authorizationrequired | header | Bearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted. |
accountNumberrequired | string | 9-18 digit numeric bank account number. |
ifscrequired | string | 11-character IFSC code, e.g. ICIC0001234. |
name | string | Beneficiary name to match. |
phone | string | Account holder's 10-digit Indian mobile number, when available; improves match confidence. |
POST /v1/verify/bank-account{
"referenceId": "SMV-20260927-3B71A0",
"valid": true,
"nameAtBank": "RAVI KUMAR",
"bankName": "ICICI Bank",
"utr": "017952100012345",
"city": "Mumbai",
"branch": "Andheri West",
"micr": "400229002",
"nameMatchScore": "100",
"nameMatchResult": "GOOD_MATCH",
"accountStatus": "ACCOUNT_IS_VALID",
"accountStatusCode": "1",
"ifscDetails": {
"bank": "ICICI Bank",
"ifsc": "ICIC0001234",
"micr": "400229002",
"address": "Andheri West, Mumbai",
"city": "Mumbai",
"state": "Maharashtra",
"branch": "Andheri West"
}
}POST /v1/verify/bank-accountBank Account: ₹4 per successful call, pay-as-you-go (lower on Growth and Scale).
POST /v1/verify/rcRequest parameters
| Field | Type | Description |
|---|---|---|
Authorizationrequired | header | Bearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted. |
vehicleNumberrequired | string | Vehicle registration number. |
POST /v1/verify/rc{
"referenceId": "SMV-20260927-3D71Q9",
"regNo": "MH12AB1234",
"registrationStatus": "VALID",
"ownerName": "RAHUL KUMAR",
"ownerFatherName": "SURESH KUMAR",
"vehicleClass": "LMV",
"vehicleManufacturerName": "MARUTI SUZUKI",
"model": "SWIFT",
"vehicleColor": "WHITE",
"chassis": "MA3ERLF1S00123456",
"engine": "K12MN1234567",
"registrationDate": "2019-05-14",
"rcStatus": "ACTIVE",
"rcExpiryDate": "2034-05-13",
"insuranceUpto": "2025-05-13",
"isCommercial": false
}POST /v1/verify/rcVehicle RC: ₹9 per successful call, pay-as-you-go (lower on Growth and Scale).
Frequently asked questions
Does using a KYC API make my NBFC KYC compliant?
No. An API check is evidence you attach to a customer file, not regulatory KYC compliance by itself. NBFCs must follow the RBI Know Your Customer Direction, 2016 as updated, and decide with their compliance team how each API result fits their policy.
In what order should I run PAN, bank account and other checks for an individual borrower?
Validate formats locally, then verify PAN with the applicant's name, then the bank account with name match, and add a secondary identity document only if your policy or an ambiguous PAN result requires it. RC applies only to vehicle-backed loans.
Which StampMitra checks help onboard a business borrower?
For businesses you can use the CIN, GSTIN and Udyam endpoints for entity facts, the PAN to GSTIN and PAN to Udyam resolvers to cross-check registrations, and the individual PAN and bank journeys for proprietors or signatories.
Should I retry a verification that returned valid false or found false?
No. StampMitra documents that negative determinations are completed, billable verifications, so retrying just pays again for the same answer. Retry only transport problems such as 429 rate limits or 502 upstream errors, with backoff and a cap.
How do I keep verification cost under control in a lending funnel?
Stage your spend: run free format validation first, then PAN, then the bank account only for applicants who pass, and business registry calls only at underwriting. Cache results inside your system and deduplicate concurrent submissions. Check live per-call prices on the pricing page.
Does StampMitra send webhooks when a verification finishes?
No. Verification endpoints are synchronous request and response. The webhooks page documents order-lifecycle events and says self-serve webhook registration is coming soon, so build your own queue and status updates for asynchronous onboarding flows.
What should go into the manual-review queue?
Send cases with partial name matches, PAN and bank name disagreements, inactive registry statuses, persistent upstream errors or registrations that do not link to the verified PAN. Attach all evidence with reference ids and timestamps, and record the analyst's decision.
What should I log, and how long should I keep verification data?
Log the product called, referenceId, timestamp, status, decision, rule version and reviewer actions, not raw identity numbers or keys. Set retention per record type with a legal basis, guided by the DPDP Act 2023, sector rules, and StampMitra's retention schedule and DPA.
Can I call the verification API from my mobile app?
You should not. API keys carry scopes that spend money, so calls belong on your backend with the key in a secrets manager. Your app talks to your own API, which queues the verification work and returns status.
Sources
- StampMitra product data and verification docs:
/docs/verification,/docs/auth,/webhooks,/pricingon developer.stampmitra.in (read 2026-10-16). - Reserve Bank of India, Master Direction - Know Your Customer (KYC) Direction, 2016 (RBI/DBR/2015-16/18, updated as on 14 August 2025): https://www.rbi.org.in/Scripts/BS_ViewMasDirections.aspx?id=11566 (verified 2026-10-16).
- The Digital Personal Data Protection Act, 2023 (No. 22 of 2023), India Code: https://www.indiacode.nic.in/handle/123456789/22037?view_type=browse and eGazette: https://egazette.gov.in/WriteReadData/2023/248045.pdf (located 2026-10-16; section references to be confirmed against the official text).
- StampMitra Developer Data Retention & Deletion Schedule and Data Processing Addendum.