StampMitraDevelopers

Guides

Building a Vendor Onboarding Flow: Which Checks to Run, in What Order

Run vendor checks in order of cost and decisiveness: offline validation, internal duplicate detection, PAN, GSTIN compared with the PAN, CIN for companies, then bank account with name match, then documents and human review. Stop at the first hard failure, persist every paid result so retries never double-bill, and never retry a bank verification blindly.

By StampMitra Developer TeamLast updated: Read this in Hindi

A vendor onboarding flow should run its checks in order of cost and decisiveness: free offline validation and internal duplicate checks first, then the PAN, then the GSTIN (compared with the PAN), then the CIN for companies, then the bank account, and finally documents and a human review for anything the automated steps could not settle. Stop at the first hard failure, because every later check on a vendor that has already failed is money spent on a decision you have already made.

The order is not arbitrary. Each step answers a question that later steps depend on, and each later step costs more than the one before. Put the steps in the wrong order and you pay a bank to verify an account for a vendor whose PAN does not exist, or you approve a vendor on a registration that belongs to somebody else.

This guide lays out the whole flow for a company that buys from vendors and pays them: what to collect, what each check protects against, the state machine that holds it together, code for an orchestrator in Node.js and Python, duplicate detection before you spend any money, what the vendor sees at every step, how to keep the vendor base clean after approval, and how to backfill vendors you already have. It uses the StampMitra verification endpoints throughout and says plainly where a check needs a human or a document.

Key takeaways

  • Order the checks by cost and decisiveness: offline validation, internal duplicate check, PAN, GSTIN with PAN comparison, CIN for companies, bank account with name match, then documents and review.
  • Stop at the first hard failure. Do not call a later, dearer endpoint for a vendor that has already failed an earlier one.
  • Check for duplicates in your own data before any API call. A duplicate PAN, GSTIN or bank account is often the most valuable signal and costs nothing.
  • Treat the PAN as the anchor. The GSTIN must embed it, the bank account name must match the verified legal name, and the CIN company name must agree.
  • Model the flow as a state machine with persisted results, so a crash or a retry never double-bills or loses progress.
  • Never retry a bank account verification blindly. It involves a bank transfer and the reference documents no idempotency for verification endpoints.
  • A changed bank account on an existing vendor is the classic fraud event. Re-run the account check and add a hold or a second approval.
  • Re-check registrations on a schedule, and before large payments. Statuses change after onboarding.
  • Verification narrows risk. It does not make a vendor trustworthy, and it does not replace contracts, due diligence on the relationship or your own policy.

What a vendor flow protects you from

Be specific about the failures you are trying to prevent. Each maps to a check, and a check you cannot tie to a risk is a candidate for removal.

RiskWhat it looks likeWhich check addresses it
A vendor that does not existA fabricated name, an invented tax numberPAN, GSTIN, CIN lookups
A real number belonging to somebody elseA genuine, active GSTIN quoted by an impostorGSTIN embedded PAN against verified PAN, name comparison
A cancelled or suspended GST registrationInvoices that cannot support input tax creditGSTIN registrationStatus and re-checks
A duplicate vendorThe same business registered twice under two names, to split approvals or hide spendInternal duplicate check on PAN, GSTIN, bank account
A payment to the wrong accountA typo or a deliberately substituted accountBank account verification with name match
A changed account at the last minuteA fraudster emailing "our bank details have changed"Re-verification and a hold on any account change
A company with no authority to contract through the applicantA person with no standing signs for the companyCIN directors, authorisation document
A conflict of interestA vendor owned by an employeeNot detectable through these APIs. A policy and declaration matter

The last row is deliberate. Verification APIs check records. They do not read intent or relationships. A vendor flow that treats them as the whole answer creates false comfort.

The order of checks, in one table

#StepWhat it costsWhat it settlesStop if
0Collect inputs by vendor typeNothingWhat you can checkThe vendor cannot supply what its type must hold
1Offline validationNothingTypos, impossible identifiersFormat, state code or check character fails
2Internal duplicate checkNothingIs this vendor already in your systemA duplicate on a hard identifier
3PAN verificationOne callThe tax identity exists with a namevalid is false
4GSTIN verificationOne callA GST registration exists, is active, and embeds the verified PANNot found, PAN mismatch, a non-active status
5CIN verification (companies)One callThe company exists, is active, and who its directors areNot found, inactive
6Bank account verificationOne call, involves a bank transferThe account exists and its holder name matchesNot valid, name far from the verified name
7Documents and human reviewStaff timeAuthority, anything the lookups could not settleThe reviewer rejects
8Activation controlsPolicyA safe first paymentThe hold is not released

The cost column explains the order. Steps one and two are free, so they run first. Steps three to five are single lookups of different cost and different decisiveness. Step six involves a bank, which makes it both the most expensive and the one you least want to repeat, so it comes last among the automated checks.

Why not run everything in parallel?

Parallel calls are faster, and for an interactive flow that sounds attractive. The trade is cost and cleanliness. If the PAN fails, a parallel run has already paid for a GSTIN lookup and a bank transfer that you did not need. Worse, a parallel bank check on a vendor whose PAN you have not yet verified has no verified name to compare against, so you are forced to use the typed name, which is the weakest candidate.

A sequential flow is rarely slow enough to matter. Each StampMitra verification is a single synchronous call, so a full company path of four calls is four round trips in sequence, not a long wait. Measure it on your own network before deciding that parallelism is needed. Where latency matters more than cost, you can run the PAN, GSTIN and CIN lookups in parallel, since they are independent and cheap, and keep the bank account check strictly after them. Do not parallelise across the bank check.

Three vendors, one morning

Here is how the order plays out for three fictional vendors submitted on the same morning.

Vendor A, a freelance designer. Submits a PAN and a bank account. Offline checks pass. No duplicates. The PAN lookup returns valid: true with a name that agrees with the typed name after normalisation. The bank check returns a valid account and a score well above the review band. Two paid calls, no human, state VERIFIED, then ACTIVE with a first-payment hold.

Vendor B, a trading company. Submits a company PAN, a GSTIN, a CIN and a bank account. Offline checks pass and no duplicate exists. The PAN is valid and the fourth letter is C. The GSTIN lookup returns Active, with the embedded PAN equal to the verified PAN, but the legalName differs from the PAN's registered name: "Pvt Ltd" against "Private Limited" resolves after normalisation, and what remains is a difference in one word that looks like a recent rename. The decision is REVIEW with NAME_DIFFERS. The flow does not stop. The CIN check runs and shows an active company whose name matches the GSTIN rather than the PAN, which supports the rename theory. The bank check passes. A reviewer sees four results and one reason, asks the vendor for the certificate of incorporation showing the name change, receives it within the day and approves. Four paid calls, one human decision.

Vendor C, a supplier with a GSTIN that is not theirs. Submits a company PAN, a GSTIN and a bank account. Offline checks pass. The duplicate check finds nothing. The PAN is valid. The GSTIN lookup returns Active, but characters 3 to 12 are a different PAN than the one just verified. Decision: FAIL with PAN_MISMATCH. The flow ends. No CIN call, no bank call, and therefore no bank transfer to an account that belongs to a stranger. The vendor receives a neutral message asking which PAN the registration was made under. Two paid calls instead of four, and a mismatch caught that a name check alone could have missed.

The three paths use the same code. The flow differs because the data differs, and the order of checks decides how much each path costs.

Step 0: collect the right inputs for each vendor type

The first decision is what to ask for. Asking every vendor for every identifier causes drop-off and pays for lookups that cannot succeed. Ask for what the vendor type can hold.

Vendor typeIdentifiersNotes
Individual or freelancerPAN, bank accountNo GST registration if below the threshold or exempt
Sole proprietorProprietor's PAN, GSTIN if registered, bank accountAccount may be in the proprietor's or the business name
Partnership firmFirm PAN, GSTIN if registered, bank accountPAN's fourth letter should be F
LLPFirm PAN, LLPIN, GSTIN if registered, bank accountCheck what the CIN endpoint accepts for LLPs on your account
CompanyCompany PAN, GSTIN, CIN, bank accountThree independent records to cross-check
Trust or societyTrust PAN, GSTIN if registered, bank accountPAN fourth letter T or A
Foreign vendorNone of theseRoute to a different process

Alongside the identifiers collect: the legal name exactly as on the PAN, the trade name if different, the registered address, a contact person and email, the vendor's own declaration of any relationship with your staff, and, for the bank account, the holder name as it appears with the bank. Ask for a cancelled cheque or bank letter as a document if your policy uses documents as the backstop.

One design point: collect the vendor type first, and let it decide which fields to show. A vendor who selects "company" sees CIN. A vendor who selects "freelancer" does not. This is how the StampMitra onboarding form itself is built.

Step 1: offline validation

Run these on your server before anything else. They are free and catch typos.

  • PAN shape: five letters, four digits, one letter; the fourth letter must agree with the claimed type.
  • GSTIN shape and state code; the check character on production keys only.
  • CIN shape (loosely, since real CINs have variations).
  • Account number: nine to eighteen digits. IFSC: four letters, a zero, six letters or digits.

The code for each is in PAN vs GSTIN vs CIN and How to verify a GSTIN programmatically. A validation failure here is a typo, so show the vendor a correction message and do not move the application forward.

Step 2: duplicate detection before you spend anything

Before any paid call, look in your own data. A vendor that already exists under the same PAN, GSTIN or bank account is the cheapest and most valuable signal in the whole flow.

sql
-- Which existing vendors share any hard identifier with the applicant?
SELECT id, legal_name, status, 'pan' AS matched_on FROM vendors WHERE pan = :pan
UNION
SELECT id, legal_name, status, 'gstin' FROM vendors WHERE gstin = :gstin
UNION
SELECT v.id, v.legal_name, v.status, 'bank_account'
FROM vendors v JOIN vendor_accounts a ON a.vendor_id = v.id
WHERE a.account_number_hash = :account_hash AND a.ifsc = :ifsc;

What the hits mean differs by identifier.

Matched onTypical meaningAction
PANThe same legal entity, possibly with a second GSTIN for another stateLink to the existing vendor and add a registration, rather than creating a second vendor
GSTINThe same registrationTreat as a duplicate and stop
Bank accountTwo vendors paid into one accountRoute to review. Legitimate in a group of companies, suspicious elsewhere

Store the bank account as a salted hash for matching and keep only the last four digits for display, so that duplicate detection does not require plaintext account numbers in more places than necessary. Names are weak duplicate evidence on their own, because "Sharma Traders" is a common name. Use them only to suggest, never to decide.

Step 3: verify the PAN

The PAN is the anchor of the rest of the flow. Call POST /v1/verify/pan with the PAN and the legal name as the vendor entered it.

cURL
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":"Jane Doe Enterprises Private Limited"}'

Branch on valid, then panStatus, then the name result and score, then type against the claimed vendor type. A valid: false stops the flow. A non-VALID status or a weak name match goes to review. The decision function, the name normaliser and the reasons for sending different names to a human rather than rejecting them are in how to verify a PAN and match the name. What the PAN call does and does not prove is in the PAN verification guide.

Keep the registeredName from this step in memory for the rest of the flow. It becomes the reference name that the bank account is compared against.

Step 4: verify the GSTIN, and compare it with the PAN

For a vendor with a GSTIN, call POST /v1/verify/gstin, then run three comparisons in code.

  1. registrationStatus must be Active. Anything else goes to review, and a cancelled or suspended registration should usually stop the vendor from being set up for GST invoicing.
  2. Characters 3 to 12 of the GSTIN must equal the verified PAN. A mismatch is a hard failure.
  3. legalName must agree with the PAN's registered name after normalisation. A difference goes to review.

If the vendor has only a PAN and says it is GST registered, use the PAN to GSTIN lookup to list its registrations and ask which one will appear on invoices. Never accept the first GSTIN in the list as the right one without confirming. The status values, what they mean and what the response does not carry are in How to verify a GSTIN programmatically.

For a vendor with no GSTIN, record why (below the threshold, exempt supplier, service provider not registered). Whether you may buy from an unregistered supplier, and how to treat the tax on those purchases, is a question for your finance and tax team. The API cannot answer it.

Step 5: verify the CIN, for companies

For a company vendor, call POST /v1/verify/cin. Check that the company is active in both cinStatus and companyStatus, compare companyName with the PAN and GSTIN names, and read the directors list. Then compare the person who applied with the directors.

If the applicant is a listed director, your policy may waive the heavier authority document, which is what StampMitra's own production access form does with the board resolution. If the applicant is not a director, ask for an authorisation letter or board resolution signed on the company's behalf. The lookup shows who the directors are. It does not show who the company has authorised.

Udyam registration, if your policy treats it as relevant for small vendors, has its own lookup; the guide on GSTIN, CIN and Udyam business verification covers it. For the broader question of what you must check for a business as against an individual, see KYC vs KYB.

Step 6: verify the bank account, last

Only vendors who passed everything above reach this step. Call POST /v1/verify/bank-account with the account number, the IFSC and, as name, the verified legal name from the PAN lookup (or the proprietor's name, depending on your policy for proprietorships). Branch on valid, then accountStatus, then the score.

Three rules, all explained in penny drop vs name match:

  1. Compare the bank name with the verified name, not with what the vendor typed.
  2. Route partial matches to review. Bank names are truncated, abbreviated and reordered.
  3. Do not retry a timed-out call blindly. The call involves a bank transfer and a retry can send a second credit. Check the logs for a completed call first.

Bank account verification is flagged as higher risk in StampMitra's production access form, which means it opens after a short extra check once your application is approved. Plan for it when you apply.

Step 7: documents and human review

Some vendors will reach this step with everything green. Others will arrive with reasons. The reviewer's screen should show: the vendor and its type; every verification result with its referenceId and time; the reasons that put the case in the queue; the documents uploaded; and the relationships derived from lookups (directors) and documents (authority).

Documents that commonly help: a cancelled cheque or bank letter for a bank-name difference; a registration certificate for a status question; an incorporation certificate for a company-name difference; an authorisation letter for a signatory who is not a director. Which documents you accept is a policy decision for your compliance owner.

Give the reviewer a closed list of actions (approve, reject, request a document, request a corrected number, escalate) and require one line of reasoning for each decision. A closed list lets you report on outcomes later, and the reasoning is what you show an auditor.

Step 8: activation and first-payment controls

Approval should not mean "pay immediately". A few cheap controls reduce the damage if something slipped through.

  • A first-payment hold. Delay the first payment to a new vendor by a period your policy sets, and require a second approver above a threshold.
  • Limits. A new vendor starts with a cap on single payments and on the monthly total, raised after a history builds up.
  • A change-of-account rule. Any change to a vendor's bank account triggers a fresh bank account verification, a hold, and a callback to a known contact through a channel the vendor did not supply in the change request.
  • A named owner. Every vendor has an internal owner who is responsible for the relationship, so that a flag has somewhere to go.

None of these are API features. They are the policy that turns verification results into safer payments.

The state machine

Persist the flow as explicit states, so that restarts, retries and reviews are well defined.

StateMeaningMoves to
DRAFTVendor is filling the formSUBMITTED
SUBMITTEDInputs receivedINVALID_INPUT, DUPLICATE, CHECKING
INVALID_INPUTAn offline check failedSUBMITTED after correction
DUPLICATEA hard identifier matches an existing vendorIN_REVIEW, CLOSED
CHECKINGPaid checks are running or queuedIN_REVIEW, REJECTED, VERIFIED
IN_REVIEWA person is decidingVERIFIED, REJECTED, NEEDS_INFO
NEEDS_INFOWaiting for a vendor document or correctionCHECKING, CLOSED
VERIFIEDAll required checks passedACTIVE
ACTIVEPayable, with limits and a holdSUSPENDED, RECHECK_DUE
RECHECK_DUEA scheduled re-check is dueACTIVE, IN_REVIEW
SUSPENDEDPayments blockedIN_REVIEW, CLOSED
REJECTED, CLOSEDEnd statesnone

Store each paid check as its own row: kind, input hash, referenceId, outcome, reasons and timestamp. The orchestrator reads the rows to know what is done, so a restart resumes instead of repeating.

An orchestrator in Node.js

JavaScript
// onboard.mjs
import { verifyPan } from "./pan-client.mjs";
import { verifyGstin } from "./gstin-client.mjs";
import { verifyCin } from "./cin-client.mjs";
import { verifyBank } from "./bank-client.mjs";
import { decidePan } from "./pan-decision.mjs";
import { decideGstin } from "./gstin-decision.mjs";
import { decideBank } from "./bank-decision.mjs";

export async function runChecks(vendor, store) {
  const done = await store.checksFor(vendor.id); // map of kind -> stored result

  async function step(kind, fn) {
    if (done[kind]) return done[kind];           // resume, never repeat a paid call
    const result = await fn();
    await store.saveCheck(vendor.id, kind, result);
    done[kind] = result;
    return result;
  }

  const pan = await step("pan", async () => {
    const res = await verifyPan({ pan: vendor.pan, name: vendor.legalName });
    return { ...decidePan(res, vendor.legalName), res };
  });
  if (pan.decision === "FAIL") return finish(vendor, store, "REJECTED", pan);
  const verifiedName = pan.res.registeredName;

  if (vendor.gstin) {
    const g = await step("gstin", async () => {
      const res = await verifyGstin({ gstin: vendor.gstin });
      return { ...decideGstin(res, { expectedPan: vendor.pan, expectedName: verifiedName }), res };
    });
    if (g.decision === "FAIL") return finish(vendor, store, "REJECTED", g);
  }

  if (vendor.type === "COMPANY") {
    const c = await step("cin", async () => {
      const res = await verifyCin({ cin: vendor.cin });
      const active = res.valid && /^active$/i.test(res.cinStatus ?? "") && /^active$/i.test(res.companyStatus ?? "");
      const applicantIsDirector = (res.directors ?? []).some((d) => d.name?.toUpperCase() === vendor.applicantName?.toUpperCase());
      return { decision: active ? "PASS" : "FAIL", reasons: active ? [] : ["COMPANY_NOT_FOUND_OR_INACTIVE"], applicantIsDirector, res };
    });
    if (c.decision === "FAIL") return finish(vendor, store, "REJECTED", c);
  }

  const bank = await step("bank", async () => {
    const res = await verifyBank({ accountNumber: vendor.account.number, ifsc: vendor.account.ifsc, name: verifiedName });
    return { ...decideBank(res), res };
  });
  if (bank.decision === "FAIL") return finish(vendor, store, "REJECTED", bank);

  const needsReview = Object.values(done).some((c) => c.decision === "REVIEW");
  return finish(vendor, store, needsReview ? "IN_REVIEW" : "VERIFIED", null);
}

async function finish(vendor, store, state, because) {
  await store.setState(vendor.id, state, because?.reasons ?? []);
  return { state, reasons: because?.reasons ?? [] };
}

Three design choices. step makes every paid call resumable: a stored result is returned instead of calling again. The loop stops at the first FAIL, so a vendor that fails the PAN never costs a bank call. And the verified name from the PAN step, not what the vendor typed, is the name the bank result is judged against.

The same flow in Python

Python
def run_checks(vendor, store):
    done = store.checks_for(vendor["id"])

    def step(kind, fn):
        if kind in done:
            return done[kind]
        result = fn()
        store.save_check(vendor["id"], kind, result)
        done[kind] = result
        return result

    pan = step("pan", lambda: {**decide_pan(res := verify_pan(vendor["pan"], vendor["legal_name"]), vendor["legal_name"]), "res": res})
    if pan["decision"] == "FAIL":
        return finish(vendor, store, "REJECTED", pan)
    verified_name = pan["res"]["registeredName"]

    if vendor.get("gstin"):
        g = step("gstin", lambda: {**decide_gstin(res := verify_gstin(vendor["gstin"]),
                                                 expected_pan=vendor["pan"], expected_name=verified_name), "res": res})
        if g["decision"] == "FAIL":
            return finish(vendor, store, "REJECTED", g)

    bank = step("bank", lambda: {**decide_bank(res := verify_bank(vendor["account"], verified_name)), "res": res})
    if bank["decision"] == "FAIL":
        return finish(vendor, store, "REJECTED", bank)

    needs_review = any(c["decision"] == "REVIEW" for c in done.values())
    return finish(vendor, store, "IN_REVIEW" if needs_review else "VERIFIED", None)

The functions verify_pan, verify_gstin, verify_bank, decide_pan, decide_gstin and decide_bank are the ones built in the endpoint guides linked above. Keep them in one module so the Node and Python services behave alike.

What the vendor sees at each step

The vendor's experience decides how many finish. Every state should have a message that says what is happening and what, if anything, they must do.

StateWhat the vendor sees
INVALID_INPUTThe exact field and a plain instruction: "The GSTIN should have 15 letters and digits. Please check what you typed."
CHECKING"We are checking your details. We will email you as soon as we have a result."
NEEDS_INFOA specific request and an upload button: "Please upload a cancelled cheque so we can confirm your account name."
IN_REVIEW"Our team is reviewing your details. We will email you within the time your procurement team has set."
VERIFIED and ACTIVEA confirmation, the vendor's code and what happens next
REJECTEDA neutral, non-accusing message with a contact for questions. Never say "fraud" or "fake"
Service trouble (timeout, 502)"We could not complete a check right now. We will try again and email you." Do not show a failure for something that is not the vendor's fault

Two further practices. Send an email at each state change, with the vendor code in the subject, so a vendor can quote it. And give procurement staff a status page showing where every vendor is and how long each has been there, because a vendor stuck in review for a week is a vendor who goes to a competitor.

Keeping the vendor base clean after approval

Approval is the start of a lifecycle.

  • Scheduled re-checks of registrations. GST registrations change status. Re-check by risk: high-spend vendors before payment runs above a threshold and monthly, regular vendors quarterly, occasional ones on next use after a year. Each re-check is a billable call with its own referenceId.
  • Event-driven checks. A bank account change, a change of authorised signatory, a name change request, a very large invoice, a vendor complaint. Each can trigger a targeted re-check.
  • Flags and owners. A status change raises a flag against the vendor's internal owner, with a deadline.
  • Dormant vendors. Vendors with no activity for a long period should be marked dormant and required to re-verify before the next payment.
  • Exit. When a vendor is closed, keep its verification record for the retention period your policy sets and block further payments.

Backfilling vendors you already have

Most teams introducing this flow already have hundreds or thousands of vendors with no verification record. Do not run them all at once.

  1. Segment by risk. Rank vendors by annual spend, by payment frequency and by whether they are paid into a bank account you have never verified. Start with the top of the list.
  2. Run the free checks first. Offline validation and duplicate detection across the whole base cost nothing and usually surface surprises: malformed numbers and the same account used by several vendors.
  3. Batch the paid checks. Run PAN and GSTIN in batches that stay inside your rate limit, with pacing and capped retries as in the bulk GSTIN guidance. Run the bank account check only for vendors you actually pay, and only once each.
  4. Treat results as review, not rejection. A legitimate old vendor with a slightly different name should land in a queue, not be blocked mid-contract.
  5. Record everything with referenceId. The backfill is the first audit trail for these vendors.
  6. Budget. Count vendors by type, multiply by the calls each type needs, and use the pricing page rates. Standard verifications such as PAN are ₹5 per call and bank account verification is ₹4 per call on pay-as-you-go, with lower plan rates. Sandbox calls are free but return sample data, so they cannot help with a real backfill.

Cost model without surprises

The cost of onboarding one vendor is the number of paid calls multiplied by their rates. The order of checks is what keeps that number low.

Vendor typeTypical paid calls on a clean pathNotes
FreelancerPAN, bank accountTwo calls
Sole proprietor with GSTPAN, GSTIN, bank accountThree calls
CompanyPAN, GSTIN, CIN, bank accountFour calls
Company that fails at PANPANOne call, because later steps never run
Duplicate vendorNoneCaught by the free duplicate check

Re-checks add calls over the vendor's life. A company vendor re-checked on a quarterly schedule adds a GSTIN call per quarter, and perhaps a CIN call. These are small, but across thousands of vendors they should be in your budget. See the pricing page for the current rates and plan discounts, and remember that every completed call is billable in production, including negative results.

Edge cases to decide in advance

  • Foreign vendors. None of these identifiers apply. Route to a separate process with its own checks.
  • Government and public bodies. The PAN fourth letter is G or L, and a CIN may not exist. Do not make a CIN mandatory for every non-individual.
  • Vendors with several GSTINs. Verify each one on invoices, and confirm they all embed the same PAN.
  • Vendors that rename or convert. A conversion from partnership to company, or a rename, can change names and numbers. Ask for the latest certificate when a lookup says not found for a number the vendor insists is theirs.
  • Group companies sharing a bank account. Legitimate and common. Route to review, record the relationship and approve with a note.
  • Individuals paid through an agency. Decide whether the agency or the individual is your vendor, and verify that one.

Metrics worth watching

MetricWhat it tells you
Time from submission to a decisionWhether vendors are being lost to delay
Share verified without reviewWhether your thresholds suit your vendors
Share rejected, by reasonWhere bad data and bad actors come from
Cases per reviewer per dayWhether the queue is sustainable
Duplicates caughtThe value of the free check
Calls per approved vendorWhether ordering is keeping costs down
Re-check flags raisedHow often registrations change under you

Common mistakes

  • Running the bank account check first, or before the PAN has passed.
  • Comparing the bank name with the typed name instead of the verified name.
  • Skipping duplicate detection, so one entity becomes two vendors.
  • Making CIN mandatory for every non-individual vendor.
  • Retrying a timed-out bank verification blindly.
  • Approving on a GSTIN without comparing its embedded PAN with the verified PAN.
  • Treating approval as permission to pay immediately, with no hold and no limits.
  • Letting a changed bank account through without re-verification and a callback.
  • Rejecting automatically on name differences instead of routing them to review.
  • Backfilling the whole vendor base in one run without pacing.
  • Using sandbox results to approve real vendors. Sandbox returns sample data.

What to do next

Get a sandbox key through the quickstart and build the flow against the documented test values. Then read the sandbox vs production testing guide before you apply for production, and errors, retries and idempotency before you ship. For what to keep from every call, read what to log and keep. Questions go to support.

API reference at a glance

PAN

PAN: paramsPOST /v1/verify/pan

Request parameters

Request parameters
FieldTypeDescription
AuthorizationrequiredheaderBearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted.
panrequiredstringPAN, format ABCDE1234F.
namestringName to match against records (max 256 chars).
PAN: responsePOST /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"
}
PAN: pricePOST /v1/verify/pan

PAN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).

GSTIN

GSTIN: paramsPOST /v1/verify/gstin

Request parameters

Request parameters
FieldTypeDescription
AuthorizationrequiredheaderBearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted.
gstinrequiredstring15-character GSTIN, format 07AAAAA1111A1Z1.
businessNamestringBusiness (legal or trade) name to cross-check against the registration.
GSTIN: responsePOST /v1/verify/gstin
{
  "referenceId": "SMV-20260927-9K21P4",
  "verificationId": "VRF-4M8Q2T7XB9KD",
  "gstin": "29AAICP2912R1ZR",
  "GSTIN": "29AAICP2912R1ZR",
  "valid": true,
  "legalNameOfBusiness": "UJJIVAN SMALL FINANCE BANK LIMITED",
  "tradeNameOfBusiness": "UJJIVAN SMALL FINANCE BANK",
  "centerJurisdiction": "I-A RANGE",
  "stateJurisdiction": "GUWAHATI - A - 1",
  "dateOfRegistration": "2017-09-30",
  "constitutionOfBusiness": "Public Limited Company",
  "taxpayerType": "Regular",
  "gstinStatus": "Active",
  "lastUpdateDate": "2022-03-01",
  "natureOfBusinessActivities": [
    "Retail Business",
    "Supplier of Services",
    "Recipient of Goods or Services",
    "Office / Sale Office",
    "Others"
  ],
  "principalPlaceAddress": "First Floor 3512-DISPUR Prithivi Mansion opp. KFC building G.S. Road, Lachit Nagar Assam 781007",
  "principalPlaceSplitAddress": {
    "buildingName": "Prithivi Mansion",
    "street": "G.S. Road",
    "location": "Lachit Nagar",
    "buildingNumber": "3512-DISPUR",
    "district": "Guwahati",
    "state": "Assam",
    "city": "Dispur",
    "flatNumber": "First Floor",
    "latitude": "26.1445",
    "longitude": "91.7362",
    "pincode": "781007"
  },
  "additionalAddresses": [
    {
      "address": "Mirza Santipur NH-37 Dist-Kamrup Rural PS-Palashbari, PO  Mirza Kamrup Assam 781125",
      "splitAddress": {
        "buildingName": "",
        "street": "NH-37",
        "location": "Santipur, Mirza",
        "buildingNumber": "",
        "district": "Kamrup Rural",
        "state": "Assam",
        "city": "Palashbari",
        "flatNumber": "",
        "latitude": "26.0926",
        "longitude": "91.5069",
        "pincode": "781125"
      }
    },
    {
      "address": "N.A.C.C.I. Foundation 3559-Tezpur CHAMBER BHAWAN Binjraj Road, P. o. and P.S. - Tezpur, Dist. Binjraj Road, P. o. and P.S. - Tezpur, Dist. Sonitpur Assam 784154",
      "splitAddress": {
        "buildingName": "CHAMBER BHAWAN",
        "street": "Binjraj Road",
        "location": "Tezpur",
        "buildingNumber": "3559-Tezpur",
        "district": "Sonitpur",
        "state": "Assam",
        "city": "Tezpur",
        "flatNumber": "",
        "latitude": "26.6338",
        "longitude": "92.8006",
        "pincode": "784154"
      }
    }
  ],
  "message": "GSTIN Exists",
  "legalName": "UJJIVAN SMALL FINANCE BANK LIMITED",
  "tradeName": "UJJIVAN SMALL FINANCE BANK",
  "registrationStatus": "Active",
  "principalPlaceOfBusiness": "First Floor 3512-DISPUR Prithivi Mansion opp. KFC building G.S. Road, Lachit Nagar Assam 781007"
}
GSTIN: pricePOST /v1/verify/gstin

GSTIN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).

Company CIN

Company CIN: paramsPOST /v1/verify/cin

Request parameters

Request parameters
FieldTypeDescription
AuthorizationrequiredheaderBearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted.
cinrequiredstringCorporate Identification Number (21 characters) or LLPIN, for example U74999DL2015PTC284009.
Company CIN: responsePOST /v1/verify/cin
{
  "referenceId": "SMV-20260927-6R38Y3",
  "verificationId": "VRF-6C2N9V4HQ7ZE",
  "valid": true,
  "status": "VALID",
  "cin": "U72900KA2015PTC082988",
  "companyName": "COMPANY NAME",
  "registrationNumber": 82987,
  "incorporationDate": "2015-09-23",
  "cinStatus": "ACTIVE",
  "email": "[email protected]",
  "incorporationCountry": "INDIA",
  "directorDetails": [
    {
      "din": "07168822",
      "name": "Frank Kelvin",
      "dob": "1997-07-21",
      "designation": "DIRECTOR",
      "address": "ST BED KORAMANGALA, BANGALORE, KARNATAKA, INDIA, 566634"
    },
    {
      "din": "07168826",
      "name": "John Doe",
      "dob": "1997-02-07",
      "designation": "DIRECTOR",
      "address": "GAUTAM NAGAR, NOIDA, UTTAR PRADESH, INDIA, 211361"
    }
  ],
  "companyStatus": "ACTIVE",
  "directors": [
    {
      "din": "07168822",
      "name": "Frank Kelvin",
      "designation": "DIRECTOR"
    },
    {
      "din": "07168826",
      "name": "John Doe",
      "designation": "DIRECTOR"
    }
  ]
}
Company CIN: pricePOST /v1/verify/cin

Company CIN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).

Bank Account

Bank Account: paramsPOST /v1/verify/bank-account

Request parameters

Request parameters
FieldTypeDescription
AuthorizationrequiredheaderBearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted.
accountNumberrequiredstring9-18 digit numeric bank account number.
ifscrequiredstring11-character IFSC code, e.g. ICIC0001234.
namestringBeneficiary name to match.
phonestringAccount holder's 10-digit Indian mobile number, when available; improves match confidence.
Bank Account: responsePOST /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"
  }
}
Bank Account: pricePOST /v1/verify/bank-account

Bank Account: ₹4 per successful call, pay-as-you-go (lower on Growth and Scale).

Frequently asked questions

In what order should vendor checks run?

Offline validation, an internal duplicate check, PAN, GSTIN compared with the verified PAN, CIN for companies, bank account with name match, then documents and review. The order follows cost and decisiveness, so a vendor that fails early never triggers a later, dearer call.

Why verify the PAN before the GSTIN and bank account?

The PAN is the anchor. Every GSTIN embeds a PAN, so you can compare it with the verified one, and the verified registered name becomes the reference the bank account name is judged against. Without it you only have what the vendor typed.

Why check for duplicates before calling any API?

A duplicate PAN, GSTIN or bank account in your own data is often the most valuable signal and costs nothing. It also stops one entity becoming two vendors, which is a common way to split approvals or hide spend.

Should bank account verification run last?

Among the automated checks, yes. It involves a bank transfer, so it is the dearest and the one you least want to repeat, and it needs a verified name to compare against. Only vendors who passed the earlier checks should reach it.

How do I avoid double-billing when a job restarts?

Persist every paid check as its own record with kind, input hash, referenceId, outcome and time, and have the orchestrator return the stored result instead of calling again. The referenceId identifies a call but is not an idempotency key, so the dedupe must be yours.

What should I do when a vendor changes their bank account?

Treat it like a new payee: re-verify the account, hold the next payment and call back a known contact through a channel the vendor did not supply in the change request. A changed account is the classic fraud event in vendor payments.

Do all vendors need a GSTIN and a CIN?

No. Ask for what the vendor type can hold: individuals have a PAN, proprietors a PAN and possibly a GSTIN, companies a PAN, GSTIN and CIN. Making CIN mandatory for everyone blocks government bodies, proprietors and many legitimate vendors.

How often should I re-check approved vendors?

By risk: high-spend vendors before payment runs above a threshold and monthly, regular vendors quarterly, occasional ones on next use after a year. Also re-check on events such as a bank account change, a change of signatory or a very large invoice.

How do I verify the vendors I already have?

Segment by risk and spend, run the free offline and duplicate checks across the whole base first, batch PAN and GSTIN calls within your rate limit, check bank accounts only for vendors you pay, treat results as review rather than rejection, and record every referenceId.

Does verification make a vendor trustworthy?

No. It confirms that records exist, are active and agree with each other. It does not detect conflicts of interest, intent to defraud or poor performance, so keep contracts, limits, holds and your own due diligence.

Sources

  • StampMitra API reference data for the PAN, GSTIN, CIN and bank account endpoints: fields, errors, scopes and billing, as in this repository on 2026-10-29.
  • StampMitra production access form: applicant types, required documents and product risk flags, as implemented in this repository on 2026-10-29.
  • GST portal: https://www.gst.gov.in and Ministry of Corporate Affairs: https://www.mca.gov.in (registration and company record concepts; confirm current rules there, 2026).
  • Income Tax Department e-filing portal: https://www.incometax.gov.in (PAN structure and holder categories, 2026).