StampMitraDevelopers

Guides

How to Verify a PAN and Match the Name

Send pan and name to POST /v1/verify/pan and read valid, panStatus, registeredName, nameMatchResult and nameMatchScore. The score is a string and the documented values are only samples, so treat results as an open set. Layer the decision: the API match, your own normalised comparison, then a human review lane, with thresholds calibrated on your own data.

By StampMitra Developer TeamLast updated: Read this in Hindi

Verifying a PAN is one call. Matching the name is where the real decisions are made. When you send both a pan and a name to POST /v1/verify/pan, the response tells you whether the PAN is valid, what its status is, the name on record, and how closely your name resembles that record in nameMatchResult and nameMatchScore. The call answers "does this number exist, and does it look like it belongs to this name?" It does not answer "is the person typing it the holder?", and it cannot settle every harmless difference between two spellings of one person's name.

Most of the pain in PAN onboarding comes from names, not numbers. The same person appears as "Rahul Kumar", "KUMAR RAHUL", "R Kumar" and "Rahul Kumar S" in different documents. A strict comparison rejects honest customers. A loose one lets in mismatches. This guide shows how to design the comparison: what to send, how to read the score, how to layer your own normalisation on top of the API's result, how to calibrate a threshold on your own data, and how to route the cases in between.

It complements two existing guides. PAN verification API for India covers the endpoint and what PAN does and does not prove, and Verify PAN in Node.js and Python covers building a production client. This one is about the name.

Key takeaways

  • One endpoint, POST /v1/verify/pan, scope verification.pan.read. Required pan; optional name (up to 256 characters). Without name you still get validity and the name on record, but no meaningful match result.
  • Send the name the way the person would write it on their PAN card, not a version you have heavily edited. Normalise on your side for comparison, not before sending.
  • nameMatchResult and nameMatchScore come back as strings in the documented sample (MATCH and "100"). The reference shows one example value for each, so treat the set of possible values as open and parse the score before comparing it.
  • Layer the decision: the API's match, then your own normalised comparison, then a human review lane. Do not treat one layer as the whole answer.
  • Choose thresholds by measuring your own data. Any number a guide gives you is a guess, including a guess about what is "good enough".
  • A name match proves the name resembles the record. It does not prove the person owns the PAN.
  • Sandbox returns sample data, so it cannot tell you how name matching behaves on real names. Test the matching policy with fixtures and calibrate with real data in production, carefully and with consent.
  • Store the referenceId, the score and your decision. Mask the PAN in logs.

What the call returns when you send a name

Request:

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":"Rahul Kumar"}'

Response in the documented shape:

JSON
{
  "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"
}
FieldWhat it tells youHow to use it for matching
validThe PAN exists in the recordBranch on this first. If false, there is no name to match
registeredNameThe name on record, upper-cased in the sampleThe string to compare against, in your own layer
nameMatchResultThe outcome of comparing your name with the recordA label; the sample shows MATCH
nameMatchScoreA score for the comparison, returned as a string ("100" in the sample)Parse it to a number before comparing
typeHolder category, for example IndividualCheck it against the entity type you expected
panStatus, panStatusCode, panStatusDescriptionStatus of the PAN as a label, a code and a sentenceDecide on the status before the name
fatherNameFather's name on recordPersonal data; store only if you have a purpose
lastUpdatedAtA date associated with the recordContext for freshness
aadhaarSeedingStatus and descriptionA link status on the recordContext only. StampMitra does not offer Aadhaar-based verification
referenceIdStampMitra's id for this callStore with your decision

Two warnings that save debugging time. First, the reference documents one example value for nameMatchResult, panStatusCode and aadhaarSeedingStatus. Do not write an exhaustive switch over values you have not seen. Add a default branch that sends unknown values to review and logs the whole response. Second, nameMatchScore is a string in this response, so "100" > "95" is false in a lexicographic comparison in many languages and "95" > "100" is true. Convert with Number() or float() first.

Why names fail to match when the person is the same

If you have not looked at a few hundred real name pairs, the failure modes are surprising. They are mostly not typos. They are conventions.

PatternExampleWhy it happens
Different order"Rahul Kumar" and "Kumar Rahul"Surname-first and given-name-first conventions differ across forms and regions
Initials"R. Kumar" and "Rahul Kumar"Many forms ask for initials, and in parts of southern India initials commonly stand for the father's or village name
Missing or extra middle name"Rahul Kumar" and "Rahul Suresh Kumar"Documents differ on whether the middle name is written
Abbreviated titles or honorifics"Mohd Aslam" and "Mohammed Aslam", "Dr Rahul Kumar"Common abbreviations and prefixes appear in some documents and not others
Spelling variants after transliteration"Shivam" and "Shivum", "Lakshmi" and "Laxmi"Names written in other scripts are transliterated to Latin in more than one way
Spacing and compound names"Ramkumar" and "Ram Kumar"Compound names are written together or apart
Changed namesMarried names, legal name changesThe record and the form were updated at different times
Single-name individuals"Rahul" and "Rahul Kumar"Some people have no surname and forms force one
Case and punctuation"rahul kumar" and "RAHUL KUMAR", "O'Neil"Cosmetic, trivial to normalise away
TruncationA long name cut at a field lengthA form or system limits the number of characters

Look at the list again. Only the last two rows are the kind of mismatch an algorithm solves reliably. The rest depend on context, and a careful design routes them to a layer that can use context: your normalisation, then a person.

What to send in the name field

Send the name as the customer wrote it from their PAN card. This is the single most useful practice, because the PAN record name is what the comparison targets. Ask for it exactly like that on your form, with a hint such as "as printed on your PAN card", and consider showing a thumbnail of where it appears.

Do not pre-clean heavily. Trimming leading and trailing whitespace is fine, and so is collapsing repeated spaces. Do not remove initials, reorder words, strip honorifics or transliterate before sending, because each of those can turn a match into a non-match when the platform's comparison would otherwise have handled the original. Keep your transformations for your own comparison layer, where you can see and tune them.

The name field takes up to 256 characters. Send nothing rather than a placeholder. Without a name you still receive validity, status and registeredName, which you can compare yourself.

Reading nameMatchResult and nameMatchScore

The documented sample returns MATCH with a score of "100". What other results and scores exist, and what thresholds separate them, is not documented in the reference, and the matching algorithm is not published. That determines how you should use them.

Treat the score as a signal, not a verdict. Higher means a closer resemblance and 100 means identical or equivalent, but you should not assume the scale is linear, that 90 is always safe, or that two different results with the same score mean the same thing. Learn the shape from your own data (see calibration below).

Treat the label as an open set. MATCH is shown. Anything else is "something other than the sample outcome" and should flow to your next layer. A good pattern is to accept the API's MATCH at or above your chosen score, reject nothing automatically on the label alone, and send every other outcome through your own comparison before deciding.

Never display the raw score to a customer. A message such as "your name matched 82%" invites arguments and reveals the policy. Show an action: confirm the name, upload a document, or wait for review.

A layered matching design

Three layers, each cheap relative to the next.

Layer 1: the API's comparison. Send name. If the result is a match at or above your score threshold, you are usually done. This layer is fast, uses the platform's own logic and costs nothing extra, because the comparison is part of the same call.

Layer 2: your normalised comparison. For anything that is not a clear match, compare registeredName from the response with the customer's input using rules you control: upper-case, strip punctuation and honorifics, expand or collapse initials, sort tokens, and handle common abbreviations. If this layer says the two are equivalent, accept with a flag. If it says they are clearly different, route to review or reject depending on the risk tier. If it is unsure, go to layer 3.

Layer 3: a human. A reviewer sees the typed name, the registered name, both scores and the document photo if you collected one, and decides. Keep the decision list short (approve, reject, ask for another document) and record who decided and why. A reviewer lane is not a failure of automation. It is the lane that keeps good customers from being rejected by a rule.

The point of layering is to make each mistake cheap. A false reject at layer 1 gets a second chance at layer 2. A false accept at layer 2 is limited by risk tier and by the downstream checks (bank account match, document review) that you run for riskier cases.

The normalising comparer, in code

Here is a compact layer-2 implementation in JavaScript. It does not claim to be the best possible. It shows the structure, so you can tune the parts that matter for your customers.

JavaScript
// name-match.mjs
const HONORIFICS = new Set(["MR", "MRS", "MS", "MISS", "SHRI", "SMT", "KUM", "DR", "PROF", "LATE", "SRI", "SHREE"]);
const ABBREVIATIONS = new Map([["MOHD", "MOHAMMED"], ["MD", "MOHAMMED"], ["MOHAMMAD", "MOHAMMED"], ["MOHAMED", "MOHAMMED"]]);

export function tokens(name) {
  return String(name ?? "")
    .toUpperCase()
    .replace(/[.'`-]/g, " ")
    .replace(/[^A-Z0-9 ]/g, " ")
    .split(/\s+/)
    .filter(Boolean)
    .filter((t) => !HONORIFICS.has(t))
    .map((t) => ABBREVIATIONS.get(t) ?? t);
}

// Two token lists are compatible when every token in the shorter list is found in the longer one,
// either exactly, or as an initial standing for a longer token.
export function compatible(a, b) {
  const [short, long] = a.length <= b.length ? [a, b] : [b, a];
  const pool = [...long];
  for (const t of short) {
    let idx = pool.indexOf(t);
    if (idx === -1 && t.length === 1) idx = pool.findIndex((p) => p.startsWith(t));
    if (idx === -1 && t.length > 1) idx = pool.findIndex((p) => p.length === 1 && t.startsWith(p));
    if (idx === -1) return false;
    pool.splice(idx, 1);
  }
  return true;
}

export function compareNames(typed, onRecord) {
  const a = tokens(typed), b = tokens(onRecord);
  if (a.length === 0 || b.length === 0) return "UNKNOWN";
  if (a.join(" ") === b.join(" ")) return "EXACT";
  if ([...a].sort().join(" ") === [...b].sort().join(" ")) return "REORDERED";
  if (compatible(a, b)) return "COMPATIBLE";
  if (a.join("") === b.join("")) return "SPACING";
  return "DIFFERENT";
}

The matching logic returns a category, not a score, on purpose. Categories are explainable to a reviewer: "same words in a different order" is a sentence a person understands, where "0.83" is not. And here is the decision that uses it together with the API's result:

JavaScript
// pan-decision.mjs
import { compareNames } from "./name-match.mjs";

const NUM = (v) => Number.parseFloat(String(v ?? "").replace(/[^0-9.]/g, ""));

export function decidePan(res, typedName, { acceptScore = 90 } = {}) {
  const ref = res.referenceId;
  if (!res.valid) return { decision: "FAIL", reasons: ["NOT_VALID"], ref };
  const reasons = [];
  if (res.panStatus !== "VALID") reasons.push(`STATUS_${String(res.panStatus ?? "EMPTY").toUpperCase()}`);

  const score = NUM(res.nameMatchScore);
  const apiMatch = res.nameMatchResult === "MATCH" && score >= acceptScore;
  if (!apiMatch) {
    const own = compareNames(typedName, res.registeredName);
    if (own === "EXACT" || own === "REORDERED" || own === "SPACING") reasons.push("NAME_EQUIVALENT_OWN_CHECK");
    else if (own === "COMPATIBLE") reasons.push("NAME_COMPATIBLE_INITIALS_OR_MIDDLE");
    else reasons.push("NAME_DIFFERENT");
  }
  const hardStop = reasons.includes("NAME_DIFFERENT");
  const needsLook = reasons.some((r) => r.startsWith("STATUS_") || r === "NAME_COMPATIBLE_INITIALS_OR_MIDDLE");
  return { decision: hardStop ? "REVIEW" : needsLook ? "REVIEW" : "PASS", reasons, ref };
}

Note the choices. Nothing in this function auto-rejects a person on a name difference. A different name goes to review, not to a dead end. A non-VALID status goes to review, because the reference documents one status and the set of others at source is larger. Only valid: false fails outright. You can tighten any of these for a high-risk product.

The same in Python

Python
import re

HONORIFICS = {"MR", "MRS", "MS", "MISS", "SHRI", "SMT", "KUM", "DR", "PROF", "LATE", "SRI", "SHREE"}
ABBREV = {"MOHD": "MOHAMMED", "MD": "MOHAMMED", "MOHAMMAD": "MOHAMMED", "MOHAMED": "MOHAMMED"}

def tokens(name):
    s = re.sub(r"[.'`-]", " ", (name or "").upper())
    s = re.sub(r"[^A-Z0-9 ]", " ", s)
    return [ABBREV.get(t, t) for t in s.split() if t not in HONORIFICS]

def compatible(a, b):
    short, long_ = (a, b) if len(a) <= len(b) else (b, a)
    pool = list(long_)
    for t in short:
        idx = pool.index(t) if t in pool else -1
        if idx == -1 and len(t) == 1:
            idx = next((i for i, p in enumerate(pool) if p.startswith(t)), -1)
        if idx == -1 and len(t) > 1:
            idx = next((i for i, p in enumerate(pool) if len(p) == 1 and t.startswith(p)), -1)
        if idx == -1:
            return False
        pool.pop(idx)
    return True

def compare_names(typed, on_record):
    a, b = tokens(typed), tokens(on_record)
    if not a or not b:
        return "UNKNOWN"
    if a == b:
        return "EXACT"
    if sorted(a) == sorted(b):
        return "REORDERED"
    if compatible(a, b):
        return "COMPATIBLE"
    if "".join(a) == "".join(b):
        return "SPACING"
    return "DIFFERENT"

The Python compare_names mirrors the JavaScript version, so a Node service and a Python batch job give the same answer on the same pair. Cross-language parity is worth a shared test file of name pairs that both implementations must pass.

Choosing and calibrating a threshold

There is no number in this guide for acceptScore, because there is no number that is right for everyone. A lender with a high fraud cost, a vendor portal with a trusted onboarding channel and a gig platform onboarding thousands of low-value payees will each choose differently. What you can do is measure.

  1. Collect a sample of real PAN verification results from your own traffic: the typed name, registeredName, nameMatchResult, nameMatchScore and your eventual human decision. Where you have no human decisions yet, run a short review phase where every non-perfect match is reviewed.
  2. Plot, or just tabulate, decisions by score band. You are looking for the band where reviewers start approving most of the cases and the band where they reject most.
  3. Count two errors separately: good customers you would have rejected at a given threshold (false rejects) and bad or mismatched ones you would have accepted (false accepts).
  4. Set the threshold where the cost of the two errors balances for your product, and set a second, lower threshold below which you stop sending cases to review and fail them outright.
  5. Revisit it every quarter, and whenever you add a new customer segment, since names in a new geography or script behave differently.

Do not copy a threshold from another team. Their customers, their risk tolerance and their review capacity are not yours. And do not tune the threshold on the same sample you report results on; keep a holdout.

Worked cases

All names here are invented. The "API result" column is illustrative: the reference shows only the MATCH and "100" sample, so real results for these pairs may differ, which is exactly why you calibrate with your own data.

Typed nameName on recordWhat your layer 2 saysSensible decision
Rahul KumarRAHUL KUMAREXACTPass
Kumar RahulRAHUL KUMARREORDEREDPass with flag
R KumarRAHUL KUMARCOMPATIBLEReview for high-risk products, pass for low-risk
Rahul S KumarRAHUL SURESH KUMARCOMPATIBLEPass with flag
Mohd AslamMOHAMMED ASLAMEXACT after abbreviationPass
Ram KumarRAMKUMARSPACINGPass
Priya SharmaPRIYA VERMADIFFERENTReview; ask whether a name changed, request a document
RahulRAHUL KUMARCOMPATIBLEReview; ask for the full name as on the card
Anita DesaiANITA DESAIEXACTPass
Suresh PatilSURESH PATELDIFFERENTReview; may be a typo or a different person

The last two rows illustrate the point of layered review. "Suresh Patil" and "Suresh Patel" are one letter apart and could be a typo or two different people. No algorithm can tell, and nor should it try to. A human with the card image can.

Design the form so fewer names fail in the first place

Half of the name mismatches you will see were created by the form. A few choices reduce them before any API call.

Ask for the name as printed on the PAN card. Put the hint next to the field, not in a help page: "Full name as on your PAN card". A small image of a sample card with the name highlighted costs little and removes ambiguity about which line to copy.

Prefer one full-name field over split first, middle and last name fields. Splitting forces people to decide where their name divides, and many cannot: compound names, single names and names with initials do not fit the pattern. Two or three boxes also tempt users to leave the middle one empty. A single field that accepts the name in the order the card prints it gives you the string the comparison wants.

Do not enforce a minimum of two words. Some people have a single name. A rule that rejects it creates exactly the customers who will invent a surname and fail the match.

Accept pasted text and do not strip characters on input. Dots in initials, hyphens and apostrophes are real parts of names. Normalise them in your comparison layer, not at the keyboard.

Show the registered name back, carefully. Showing the user the name on record ("We found this name: RAHUL KUMAR. Is this you?") can resolve a mismatch by confirmation, but it also reveals personal data to whoever is typing. Use it only after a step that makes the person more likely to be the holder, and never on a public page.

Pre-fill from a trusted source where you have one. If the customer is already known to you and the name came from a previous verified check, reuse it and compare, instead of asking again.

What to ask when a case lands in review

The review lane works best when the first message to the customer is specific, short and neutral. Avoid wording that sounds like an accusation. Examples that work:

  • "The name you entered is slightly different from the name on this PAN. Please enter your name exactly as it appears on your PAN card."
  • "Our check found a different surname from the one you entered. If your name changed after marriage or for another reason, please upload a document that shows the change."
  • "You entered only a first name. Please give your full name as printed on your PAN card."

Decide in advance which documents your policy accepts for each reason: an image of the PAN card for a typing or order difference, and a document showing a name change for a changed name. Which documents are acceptable is a policy and legal question for your compliance owner, not a question for this guide. What matters technically is that your review screen offers the reviewer a closed list of actions (approve, reject, request document, escalate), and that the customer's reply is attached to the same case, so that nobody has to reconstruct the story from emails.

Give the customer a way to correct a typo without starting again. Most mismatches in the review lane are resolved by the first reply, and a flow that forces a fresh application loses people.

Entities, firms and trusts: names that are not people

The same call verifies a PAN held by a company, a firm or a trust, and the name comparison faces a different set of conventions.

PatternExampleHandling
Legal form abbreviations"Pvt Ltd" and "Private Limited", "LLP", "Ltd" and "Limited"Normalise both to one form before comparing
Connectors"&" and "and"Map to a single token
Prefix conventions"M/s Sharma Traders"Strip the prefix
Dropped legal form"Sharma Traders" against "SHARMA TRADERS PRIVATE LIMITED"Compatible, but check the entity type
Brand versus legal nameBrand on the website, legal name on recordCompare to the legal name, and ask for a certificate if unsure
Punctuation"A.B.C. Industries" and "ABC INDUSTRIES"Remove dots and compare

For entities, the check that matters more than the match score is the entity type. A company PAN has a fourth letter C, a firm F, a trust T. A name that matches but belongs to an individual PAN means somebody supplied a proprietor's number for a company. Run the entity-type check first. The wider flow for businesses is described in KYC vs KYB and in PAN vs GSTIN vs CIN.

Watching match quality over time

A matching policy is not finished at launch. Customer mix changes, new regions arrive, form changes alter the strings you receive, and the platform's own comparison can change. A handful of numbers tell you when the policy needs attention.

MeasureWhat a change means
Share of verifications that pass without reviewA drop suggests a form change, a new segment or a stricter threshold; a rise may mean the threshold is too loose
Share routed to review, split by reasonShows whether initials, reorderings or real differences dominate, and therefore what to fix
Reviewer overturn rateIf reviewers approve nearly every case routed for a reason, that rule is too strict; if they reject nearly all, it may be too loose
Time in the review laneLong waits cost customers
Category mix from your own comparerA new spike in DIFFERENT for one channel can indicate abuse or a broken form
Distribution of nameMatchScoreA visible shift can signal that the upstream comparison changed. Raise it with support using referenceId values

Record the reasons as codes (the decision function above does) so these numbers are a query, not a research project. Review them monthly at first and quarterly once stable.

What a name match does not prove

A matching name shows that the number exists and resembles the name the customer typed. It does not show the person typing is the holder. PANs are not secret, and anyone can read a name from a photograph and type it. If your risk model requires ownership, add a step that proves the person has the document or the account: a document photograph reviewed by a person, a bank account verification where the holder name must also match (see penny drop vs name match), or another factor your policy accepts. StampMitra does not offer Aadhaar-based verification, so do not plan a flow that depends on it.

Likewise, a match says nothing about the person's address, creditworthiness, residence or whether they are who they claim to be in any other context. PAN is a tax identifier. For the scope of what it proves, see PAN vs GSTIN vs CIN.

Use the holder type as a second check

The response includes type (the sample shows Individual). The fourth letter of the PAN encodes the holder category, so you can compare both with the entity type the customer claimed. A person who says they are a private company and supplies a PAN whose fourth letter is P, or whose type says Individual, has either supplied the wrong PAN or is not a company. Route it to a clarifying question. This is a free check that catches a class of errors name matching alone does not: an individual's PAN with the company's name typed in.

Status first, name second

Decide on the PAN's status before you worry about the name. valid: false ends the process. For valid: true, look at panStatus, panStatusCode and panStatusDescription. The sample shows VALID, E and Existing and valid PAN. The record at source can hold other statuses, and the reference does not list them, so write "VALID passes, everything else goes to review", log the raw values and extend your table as you learn what appears in your traffic.

lastUpdatedAt is the date associated with the record, which gives context but is not the time of your call. For how freshness works, and why an API result can differ from what a person sees on a portal, read why results differ from the government portal.

Testing the matching policy

Sandbox helps with plumbing and not with matching. SNDBX0000A returns a valid PAN with a synthetic name, TEST USER, and SNDBX9999F returns not valid. Every other input gets a stable but arbitrary outcome. A sandbox call never compares your name with a real record, so the score you see there says nothing about how the comparison treats "R Kumar" against "RAHUL KUMAR".

Split your testing accordingly.

  • Fixtures for the policy. Record or hand-write responses with different nameMatchResult and nameMatchScore values and feed them to decidePan. Test that unknown labels go to review, that a string score is parsed, and that a valid: false fails.
  • Unit tests for compareNames. A table of pairs and expected categories, shared between your Node and Python code.
  • Sandbox for the HTTP path. The magic values, a malformed PAN (400), a wrong key (401), a missing scope (403).
  • Production smoke test. A few calls with your own PAN and name, and a couple of variants of your own name that you are entitled to test, to see real scores. Handle the results as personal data.

The guide on sandbox vs production: what you can test covers the rest of the go-live path.

Errors, retries and cost

A malformed PAN fails fast with a 400 (pan must match the format ABCDE1234F) and costs nothing. A wrong key is 401, a missing scope is 403, an empty wallet is 402, a rate limit is 429 and an unreachable upstream is 502. Retry only 429 and 502, with jittered backoff and a cap. A valid: false is a completed lookup that is billed in production. A PAN lookup is ₹5 per call on pay-as-you-go and lower on plans; check the pricing page. Because the name comparison is part of the same call, there is no extra charge for including name, which is why you should send it whenever you have it. The details of handling every status are in errors, retries and idempotency.

Common mistakes

  • Pre-cleaning the name before sending, so a good match becomes a worse one.
  • Comparing nameMatchScore as a string.
  • Hard-coding a list of nameMatchResult values from one sample.
  • Rejecting automatically on a name difference, with no review lane.
  • Copying a threshold from a blog post or another team.
  • Showing the raw score to the customer.
  • Treating a name match as proof the person owns the PAN.
  • Forgetting to check type against the claimed entity type.
  • Believing a sandbox score says anything about real names.
  • Logging the full PAN and the name together.

What to do next

If you do not have a key, the quickstart takes about five minutes. Read the PAN endpoint reference and the PAN product page, then build the layered decision above. For the order of checks on a vendor or borrower, read Building a vendor onboarding flow. For a payee's bank account, read penny drop vs name match. 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).

Frequently asked questions

How do I verify a PAN and match the name with an API?

Send a POST to /v1/verify/pan with pan and name. The response gives valid, panStatus, registeredName, type, nameMatchResult and nameMatchScore. Branch on valid first, then on status, then on the name result, and store the referenceId with your decision.

What should I put in the name field?

Send the name as the customer would write it from their PAN card, up to 256 characters. Avoid heavy pre-cleaning such as removing initials or reordering words, because that can turn a match into a non-match. Keep your own normalisation for your comparison layer.

Is nameMatchScore a number?

In the documented response it is a string, shown as 100 in the sample. Convert it with Number or float before comparing, because string comparison orders values lexicographically and gives wrong answers for scores such as 95 and 100.

What score counts as a good match?

The reference does not publish thresholds or the matching algorithm, and any number in a guide is a guess. Collect your own results with reviewer decisions, compare false rejects and false accepts by score band, and set your threshold from that data.

Why do names fail to match when it is the same person?

Mostly because of conventions rather than typos: different order, initials, missing middle names, Mohd versus Mohammed, transliteration variants, compound names, changed names and single-name individuals. Layer your own normalisation and a human review lane on top of the API result.

Should I automatically reject a name mismatch?

Usually not. Send different names to review with a specific question to the customer, and reserve automatic rejection for invalid PANs. A reviewer with the card image can resolve cases such as a one-letter spelling difference that no algorithm can settle.

Does a name match prove the person owns the PAN?

No. It shows that the number exists and resembles the typed name. PANs are not secret. If you need ownership, add a step such as reviewed document images or a bank account check where the holder name must also match. StampMitra does not offer Aadhaar-based verification.

Can I test name matching in the sandbox?

Only the plumbing. Sandbox returns sample data, such as the synthetic name TEST USER for SNDBX0000A, and never compares your name with a real record. Test your policy with fixtures and calibrate with real data in production, with consent.

How do I use the holder type from the response?

Compare type, and the fourth letter of the PAN, with the entity type the customer claimed. An individual PAN submitted for a company is a free catch that name matching alone misses. Route a mismatch to a clarifying question.

What should I store from a PAN verification?

Store the referenceId, your decision, the reason codes, the score and the timestamp. Think twice before storing registeredName and fatherName, and mask the PAN in logs. Retention depends on your policy and sector rules, so settle it with your compliance owner.

Sources

  • Income Tax Department, e-filing portal: https://www.incometax.gov.in (PAN structure and holder categories; confirm current rules there, 2026).
  • StampMitra API reference data for /v1/verify/pan: request fields, response fields, errors, scopes and billing, as in this repository on 2026-10-29. The reference documents one example value for several enumerated fields.
  • StampMitra sandbox test values, copied from the sandbox responder, 2026-10-29.
  • Digital Personal Data Protection Act, 2023 (general framework for personal data), India Code: https://www.indiacode.nic.in (2026).