PAN, GSTIN and CIN answer three different questions. A PAN says which taxpayer an income tax record belongs to. A GSTIN says that a taxpayer holds a registration under GST in one particular state. A CIN says that a company was incorporated with the Registrar of Companies and carries a company record. They are issued by different authorities, they have different structures, and a successful lookup on one tells you almost nothing about the other two.
The confusion in onboarding flows is usually about order and substitution. Teams ask for a GSTIN and treat it as proof the business is a company. They check a company's CIN and assume its tax registration is fine. They validate a PAN and call the vendor "verified". This guide separates the three: what each identifier is made of, who issues it, what a lookup returns through the StampMitra API, what it proves and what it leaves open, and which one to ask for from which kind of counterparty.
It is written for developers wiring checks into onboarding, and for the compliance or finance person who has to decide which checks a vendor, borrower or merchant must clear. Every field and endpoint shown comes from the StampMitra API reference, and anything the reference does not document is marked so you can ask before you build on it.
Key takeaways
- A PAN identifies a taxpayer for income tax. It is ten characters, and its fourth letter tells you the type of holder (individual, company, firm, trust and so on).
- A GSTIN identifies one GST registration. It is fifteen characters: a state code, the holder's PAN, an entity number, a fixed
Zand a check character. One PAN can therefore sit behind many GSTINs. - A CIN identifies a company or similar body registered with the Registrar of Companies. It is twenty-one characters and encodes listing status, industry, state, year and ownership type. It does not contain the PAN.
- A lookup confirms that a record exists and returns what the source holds, at the time the source was last refreshed. It does not prove the person in front of you is the holder, that the business is trading, or that the numbers are the same entity unless you compare names yourself.
- Ask for the identifier that matches the entity type: PAN for individuals, PAN plus GSTIN for proprietors and firms that charge GST, PAN plus GSTIN plus CIN for companies.
- Cheap offline checks (length, character classes, state code, GSTIN check character) catch typos before you spend a call. Note that documented sample and sandbox values do not carry a valid GSTIN check character, so keep that check out of sandbox.
- Each StampMitra lookup is a separate call with its own endpoint, scope and
referenceId. Cross-checking two of them is your code, not a feature of one response.
The three identifiers side by side
This is the table most readers came for. Read the last two columns first: they are where onboarding mistakes happen.
| PAN | GSTIN | CIN | |
|---|---|---|---|
| Stands for | Permanent Account Number | Goods and Services Tax Identification Number | Corporate Identification Number |
| Issuer | Income Tax Department | GST authorities, through the GST portal | Registrar of Companies under the Ministry of Corporate Affairs |
| Identifies | A taxpayer (person or entity) | One GST registration of a taxpayer in one state or union territory | A registered company |
| Length and shape | 10 characters: 5 letters, 4 digits, 1 letter | 15 characters: 2-digit state code, 10-character PAN, entity number, Z, check character | 21 characters: listing letter, 5-digit industry code, 2-letter state, 4-digit year, 3-letter ownership code, 6-digit number |
| Held by | Individuals, firms, companies, trusts and other taxpayers | Taxpayers registered under GST | Companies (not sole proprietors, not most partnership firms) |
| A successful lookup proves | A PAN exists with a given status and a name on record | A registration exists, with its legal name, trade name, state and status | A company record exists, with its name, status and listed directors |
| It does not prove | That your applicant is that person; that the business exists or trades | That the PAN holder is who you think; that returns are filed; that the goods or services are what is invoiced | That the company is solvent or active in business; that your signatory is authorised; the company's tax compliance |
| Typical use | Identity and taxpayer anchor for people and entities | Vendor onboarding, invoice and input tax credit checks, merchant onboarding | Company onboarding, KYB, lending to a company, contract counterparty checks |
| StampMitra endpoint | POST /v1/verify/pan | POST /v1/verify/gstin | POST /v1/verify/cin |
Two relationships are worth fixing in your head now. The PAN is literally embedded inside every GSTIN, between the state code and the entity number. And the CIN stands apart: there is no formula that turns a CIN into a PAN or a GSTIN, so the link between a company and its tax identifiers has to come from lookups and name comparison.
Reading a PAN character by character
A PAN has the pattern AAAAA9999A. Five letters, four digits, one letter. The pattern is public and stable, and the StampMitra PAN endpoint validates it before anything else: a malformed value fails fast with a 400 and the documented message pan must match the format ABCDE1234F.
| Position | Meaning |
|---|---|
| 1 to 3 | Alphabetic series. Sequential, with no meaning you can decode. |
| 4 | Holder type (table below). |
| 5 | First letter of the holder's surname for an individual, or of the entity's name for other types. |
| 6 to 9 | Sequential number. |
| 10 | Alphabetic check letter. |
The fourth character is the part with business value, because it lets you reject an obviously wrong identifier without a network call.
| Fourth letter | Holder type |
|---|---|
P | Individual |
C | Company |
H | Hindu Undivided Family |
F | Firm, including a limited liability partnership |
A | Association of persons |
B | Body of individuals |
T | Trust |
L | Local authority |
J | Artificial juridical person |
G | Government |
Use it as a consistency test. If an applicant says they are a private limited company and hands you a PAN whose fourth letter is P, something is off: either they gave their personal PAN or they are not a company. That is a reason to ask a follow-up question, not an automatic rejection, because mistakes are common. The StampMitra onboarding form uses the same idea: it expects P for an individual or proprietor, F for a partnership or LLP, C for a company and T or A for a trust or society.
The PAN response also returns type (for example Individual), so you can compare the returned holder type with the fourth letter and with what the applicant claimed.
Reading a GSTIN
A GSTIN is fifteen characters and every part has a job.
| Position | Meaning | Example from the docs sample 07AAAAA1111A1Z1 |
|---|---|---|
| 1 to 2 | GST state or union territory code | 07 (Delhi) |
| 3 to 12 | PAN of the registered taxpayer | AAAAA1111A |
| 13 | Entity number: how many registrations this PAN holds in that state, counting from 1, then A to Z | 1 |
| 14 | Fixed character Z | Z |
| 15 | Check character, computed from the first fourteen | 1 |
State codes are not postal codes
The two-digit prefix is the GST state code, which follows its own list. Delhi is 07, Uttar Pradesh 09, Maharashtra 27, Karnataka 29, Tamil Nadu 33, Telangana 36. Union territories and a few special codes have their own numbers. Your validation code should carry the full list, because an unknown prefix is a free and instant rejection.
Why one PAN gives many GSTINs
GST registration is per state. A company with offices and warehouses in three states holds three GSTINs, all containing the same PAN. A company with two separately registered business verticals in one state can hold two GSTINs in that state, which is what the thirteenth character counts. So "does this PAN have a GSTIN?" is the wrong shape of question. The right questions are "which GSTIN is on this invoice, and does it belong to the PAN I verified?" and "which state does the invoice's place of supply correspond to?". For the first direction, the PAN to GSTIN lookup returns the list of GSTINs attached to a PAN, each with state and status.
The check character
The fifteenth character is a checksum over the first fourteen. It uses the characters 0 to 9 and A to Z as base 36 digits, multiplies alternating positions by one and two, folds each product back into base 36, sums them, and takes the complement modulo 36. That is enough to catch most single-character typos and many transpositions. Section "Offline checks before you spend a call" has working code. One practical warning belongs here: the sample GSTIN in the StampMitra docs and the documented sandbox GSTINs do not have a valid check character. They are test values. If you add a check-character test to your form, make it production-only, or your sandbox tests will fail before they reach the API.
Reading a CIN
A CIN has twenty-one characters. Take the documented sample U74999DL2015PTC284009.
| Characters | Sample | Meaning |
|---|---|---|
| 1 | U | L for a company listed on a stock exchange, U for unlisted |
| 2 to 6 | 74999 | Five-digit industry code based on the national industry classification |
| 7 to 8 | DL | State or union territory of the registered office |
| 9 to 12 | 2015 | Year of incorporation |
| 13 to 15 | PTC | Ownership or company type. Common values include PTC (private company), PLC (public company), GOI (government company), NPL (not-for-profit licensed company) and FTC (subsidiary of a foreign company) |
| 16 to 21 | 284009 | Registration number assigned by the Registrar |
Two properties surprise people. First, a CIN is not frozen for life: the listing letter, the state code and the industry code describe the company at some point in time, and the identifier is typically reissued when those details change in the register, such as when a company lists or moves its registered office to another state. Store the CIN you verified together with the date you verified it. Second, the CIN carries no PAN. A company has its own PAN, but you obtain it from the company and check it with a PAN lookup; you cannot derive it.
Limited liability partnerships and others
An LLP does not get a CIN in the company sense. It is identified by an LLPIN, which looks like three letters, a hyphen and four digits. A sole proprietorship has no registration of its own at the company registry at all: its identity is the proprietor's PAN, plus whatever GST or Udyam registration the business holds. A registered partnership firm has a firm PAN (fourth letter F) but is registered with a state registrar of firms rather than the company registry. If your onboarding takes in LLPs, check the CIN verification reference for the exact inputs accepted on your account, rather than assuming an LLPIN goes into the same field.
Udyam is a fourth identifier you will meet for micro, small and medium enterprises. It has its own lookup and its own guide: GSTIN, CIN and Udyam business verification.
What a PAN lookup proves, and what it leaves open
A call to POST /v1/verify/pan returns, among other fields, valid, type, registeredName, panStatus and panStatusDescription, and a nameMatchResult with nameMatchScore when you also send a name. The documented fields are in the reference table later in this guide.
What that proves: the number is in the record, the record has a status you can read, and the name on record resembles the name you supplied to a measurable degree.
What it does not prove is more important than what it does. It does not prove that the person typing into your form is the PAN holder. A PAN is not a secret: people share photographs of it routinely, and a lookup only checks that a number exists. If your risk model needs "this person owns this PAN", you need a separate liveness or ownership step, and StampMitra does not offer Aadhaar-based verification. It does not prove address, because PAN is not an address document. It does not prove the business exists, trades or is solvent. And it reflects the source at its last refresh, which the guide on why API results differ from the portal covers in detail.
For a longer treatment of the PAN endpoint itself, read the PAN verification API guide.
What a GSTIN lookup proves, and what it leaves open
POST /v1/verify/gstin takes a gstin and an optional businessName and returns valid, legalName, tradeName, registrationStatus, principalPlaceOfBusiness and a message. The returned GSTIN field echoes the number checked.
What that proves: a registration with that number exists, it is registered to a legal name and a trade name, and it has a status. For vendor onboarding, registrationStatus is the field that matters most. The documented sample shows Active. Other statuses exist at source, such as cancelled and suspended registrations, and the reference does not enumerate them, so write your code as "Active passes, everything else goes to review" and log the raw value.
What it does not prove: that returns are being filed, that tax is being paid, or that the entity behind the registration is the one you are contracting with. A vendor can hand you a real, active GSTIN that belongs to someone else. The defence is name comparison, which is why the endpoint accepts businessName, and PAN comparison, which is why you should split the GSTIN and check characters 3 to 12 against the vendor's verified PAN. The principal place of business is useful context, not proof of where goods ship from.
The deeper treatment, including status handling and input rules, is in How to verify a GSTIN programmatically.
What a CIN lookup proves, and what it leaves open
POST /v1/verify/cin takes a cin and returns valid, companyName, incorporationDate, cinStatus, companyStatus, a directors array (each with din, name and designation), a directorDetails array that adds dob and address to each director, email, registrationNumber and incorporationCountry.
What that proves: the company exists on the register, with a status and a set of directors as of the register's last update. That makes it the identifier of choice for the "who can sign for this company" question, because you get names and DINs to compare with the person who is applying.
What it does not prove: that the person applying is one of those directors or is authorised by them (you need a board resolution or authorisation letter for that, which is why the StampMitra production access form asks for exactly those documents), that the company is operating, or that its tax registrations are in order. A company can be on the register and have no valid GST registration, and a company can have a GSTIN under a different name after a rename that has not propagated.
How the three connect, and where they never do
The links run in one direction only.
- GSTIN contains PAN. You can extract characters 3 to 12 and compare them with any PAN you have verified.
- PAN does not contain GSTIN. You need the PAN to GSTIN lookup, or the vendor to tell you.
- CIN contains neither. To connect a company's CIN to its PAN, ask the company for the PAN, verify it, and compare the returned PAN name with the CIN company name.
- PAN type (fourth letter) should agree with the entity type:
Cfor a company,Ffor a firm or LLP,Pfor an individual or proprietor.
This gives you a short list of consistency checks that cost nothing beyond the calls you were already making:
- Fourth letter of the PAN matches the claimed entity type.
- Characters 3 to 12 of the GSTIN equal the verified PAN.
- State code of the GSTIN is plausible for the address the vendor gave you (a vendor in Pune with a Karnataka registration is possible, but you should know why).
- Legal name from the GSTIN lookup, registered name from the PAN lookup and company name from the CIN lookup agree after normalisation (case, punctuation, "Private Limited" versus "Pvt Ltd").
- For companies, the person applying appears among the CIN
directorsor has an authorisation document.
Which identifier to ask for, by counterparty type
Asking for everything from everyone creates drop-off and costs money for no gain. Ask for what the entity type can actually hold.
| Counterparty | Ask for | Why | Optional extras |
|---|---|---|---|
| Individual (customer, freelancer, tenant) | PAN | The only tax anchor an individual has | Bank account for payouts |
| Sole proprietor | Proprietor's PAN, GSTIN if the business charges GST | The business is the person | Udyam number, bank account |
| Partnership firm | Firm PAN, GSTIN if registered | A firm PAN has fourth letter F | Partnership deed, authorisation letter |
| LLP | Firm PAN, LLPIN, GSTIN if registered | Registered body with its own registry number | Authorisation letter |
| Private or public company | Company PAN, GSTIN, CIN | Three independent records you can cross-check | Board resolution, director PAN |
| Trust or society | Trust PAN, GSTIN if registered | Fourth letter T or A | Trust deed, authorisation letter |
For the full decision of which checks to run in which order on a vendor, see Building a vendor onboarding flow. For individual and business borrowers specifically, see KYC vs KYB.
Offline checks before you spend a call
Format checks are free, instant and catch the most common input errors. They also keep the 400 error path for the cases you did not anticipate. Here is a compact JavaScript version for all three.
// identifiers.mjs
const PAN_RE = /^[A-Z]{5}[0-9]{4}[A-Z]$/;
const GSTIN_RE = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/;
const CIN_RE = /^[LU][0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$/;
const PAN_TYPES = { P: "Individual", C: "Company", H: "HUF", F: "Firm or LLP", A: "AOP", B: "BOI", T: "Trust", L: "Local authority", J: "Artificial juridical person", G: "Government" };
export function checkPan(raw) {
const pan = String(raw ?? "").trim().toUpperCase();
if (!PAN_RE.test(pan)) return { ok: false, reason: "FORMAT" };
const holder = PAN_TYPES[pan[3]];
if (!holder) return { ok: false, reason: "HOLDER_TYPE" };
return { ok: true, pan, holder };
}
const CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function gstinCheckChar(first14) {
let sum = 0;
for (let i = 0; i < 14; i++) {
const product = CHARSET.indexOf(first14[i]) * (i % 2 === 0 ? 1 : 2);
sum += Math.floor(product / 36) + (product % 36);
}
return CHARSET[(36 - (sum % 36)) % 36];
}
export function checkGstin(raw, { checkDigit = true } = {}) {
const gstin = String(raw ?? "").trim().toUpperCase();
if (!GSTIN_RE.test(gstin)) return { ok: false, reason: "FORMAT" };
if (checkDigit && gstinCheckChar(gstin.slice(0, 14)) !== gstin[14]) return { ok: false, reason: "CHECKSUM" };
return { ok: true, gstin, stateCode: gstin.slice(0, 2), embeddedPan: gstin.slice(2, 12) };
}
export function checkCin(raw) {
const cin = String(raw ?? "").trim().toUpperCase();
if (!CIN_RE.test(cin)) return { ok: false, reason: "FORMAT" };
return { ok: true, cin, listed: cin[0] === "L", stateCode: cin.slice(6, 8), year: Number(cin.slice(8, 12)) };
}Set checkDigit to false whenever the key in use starts with sk_test_. The documented sample 07AAAAA1111A1Z1 and the sandbox GSTINs fail the check character, which is expected for placeholders.
Why the CIN pattern here is deliberately loose
Real CINs have shown variations in the ownership code and in older entries, so the pattern above accepts any three letters in that slot. Be loose on the client and let the API be the authority. A false reject at your form costs you a customer; a false accept costs you one call.
The same checks in Python
import re
PAN_RE = re.compile(r"^[A-Z]{5}[0-9]{4}[A-Z]$")
GSTIN_RE = re.compile(r"^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$")
CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def gstin_check_char(first14: str) -> str:
total = 0
for i, ch in enumerate(first14):
product = CHARSET.index(ch) * (1 if i % 2 == 0 else 2)
total += product // 36 + product % 36
return CHARSET[(36 - total % 36) % 36]
def split_gstin(raw: str, check_digit: bool = True) -> dict:
gstin = (raw or "").strip().upper()
if not GSTIN_RE.match(gstin):
return {"ok": False, "reason": "FORMAT"}
if check_digit and gstin_check_char(gstin[:14]) != gstin[14]:
return {"ok": False, "reason": "CHECKSUM"}
return {"ok": True, "gstin": gstin, "state_code": gstin[:2], "embedded_pan": gstin[2:12]}Calling the three endpoints
All three are POST endpoints under /v1/verify/, authenticated with Authorization: Bearer <key> (the x-api-key header is also accepted), and each needs its own scope on the key: verification.pan.read, verification.gstin.read and verification.cin.read. A key without the scope gets a 403 with code FORBIDDEN. Here are the three calls with the documented sample values.
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"}'
curl -X POST "https://api-b2b.stampmitra.in/v1/verify/gstin" \
-H "Authorization: Bearer $STAMPMITRA_API_KEY" -H "Content-Type: application/json" \
-d '{"gstin":"07AAAAA1111A1Z1","businessName":"Jane Doe Enterprises"}'
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"}'The same flow from Node.js, with the three results gathered in parallel:
const base = "https://api-b2b.stampmitra.in/v1/verify";
const headers = { Authorization: `Bearer ${process.env.STAMPMITRA_API_KEY}`, "Content-Type": "application/json" };
async function call(path, body) {
const res = await fetch(`${base}/${path}`, { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(10_000) });
const json = await res.json();
if (!res.ok) throw Object.assign(new Error(json.error?.message ?? "request failed"), { status: res.status, requestId: json.error?.requestId });
return json;
}
const [pan, gstin, cin] = await Promise.all([
call("pan", { pan: "ABCDE1234F", name: "Jane Doe Enterprises Private Limited" }),
call("gstin", { gstin: "07AAAAA1111A1Z1", businessName: "Jane Doe Enterprises" }),
call("cin", { cin: "U74999DL2015PTC284009" }),
]);
console.log(pan.referenceId, gstin.referenceId, cin.referenceId);And in Python:
import os, requests
BASE = "https://api-b2b.stampmitra.in/v1/verify"
HEADERS = {"Authorization": f"Bearer {os.environ['STAMPMITRA_API_KEY']}", "Content-Type": "application/json"}
def call(path: str, body: dict) -> dict:
r = requests.post(f"{BASE}/{path}", json=body, headers=HEADERS, timeout=10)
data = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {data['error']['code']} request={data['error']['requestId']}")
return data
cin = call("cin", {"cin": "U74999DL2015PTC284009"})
print(cin["companyName"], cin["cinStatus"], [d["din"] for d in cin["directors"]])Response fields that matter for each identifier
| Endpoint | Fields to branch on | Fields to store | Fields to treat as personal data |
|---|---|---|---|
| PAN | valid, panStatus, type, nameMatchResult | referenceId, pan (masked in logs), nameMatchScore, lastUpdatedAt | registeredName, fatherName |
| GSTIN | valid, registrationStatus | referenceId, GSTIN, legalName, tradeName | Proprietor names inside legalName |
| CIN | valid, cinStatus, companyStatus | referenceId, registrationNumber, incorporationDate | directors[].name, directors[].din, email |
Every successful verification response carries a referenceId in the form SMV-YYYYMMDD-XXXXXXX. It is present on negative results too, because a negative determination is a completed lookup and is billed. It identifies the call; it does not deduplicate retries.
A worked example: onboarding a private limited vendor
A fictional trading company, Jane Doe Enterprises Private Limited, applies to be a vendor. The applicant gives you three numbers and a person's name.
- Offline: the PAN's fourth letter is
C, which agrees with "private limited company". The GSTIN embeds the same PAN. The CIN has aPTCownership code. No call spent, three consistency checks passed. - PAN call:
validis true,typereflects a company,registeredNameisJANE DOE ENTERPRISES PRIVATE LIMITED. - GSTIN call:
registrationStatusisActive,legalNamematches the PAN name,principalPlaceOfBusinessis in Bengaluru, and the state code is29for Karnataka. The vendor's invoice address is Bengaluru, so there is nothing to explain. - CIN call:
cinStatusandcompanyStatusare active,incorporationDateis2015-03-12, anddirectorslists a director named Rahul Kumar. - The person applying is Rahul Kumar. His authority is plausible but not proven by the lookup, so you ask for a board resolution or authorisation letter, as the onboarding process on StampMitra itself does for companies.
Total cost: three calls. The decision would have been different if the GSTIN lookup had returned a status other than Active, if its legal name had differed from the PAN name beyond punctuation, or if no director matched the applicant. In each of those cases the right action is a manual review with the three referenceId values attached, not an automatic rejection.
How to store the three identifiers together
Most teams start with three text columns on a vendor row and regret it. The identifiers have different lifetimes and different relationships, so model them that way. Keep one record for the legal entity, one row per tax registration, and one row per verification event.
{
"entity": {
"id": "ent_1042",
"claimedType": "PRIVATE_LTD",
"pan": "ABCDE1234F",
"cin": "U74999DL2015PTC284009"
},
"registrations": [
{ "gstin": "07AAAAA1111A1Z1", "stateCode": "07", "lastStatus": "Active", "lastCheckedAt": "2026-10-29" }
],
"verifications": [
{ "referenceId": "SMV-20260927-8F42K7", "kind": "pan", "checkedAt": "2026-10-29", "outcome": "valid" },
{ "referenceId": "SMV-20260927-9K21P4", "kind": "gstin", "checkedAt": "2026-10-29", "outcome": "valid" }
]
}Three reasons for this shape. A PAN belongs to the entity and rarely changes, while GSTINs come and go as the business opens or closes state registrations, so they need their own rows. A status is a fact about a moment, so it carries lastCheckedAt and you refresh it on a schedule instead of trusting it forever. And a verification row that points back to the referenceId gives you the audit link without storing the full response body in your primary database. The guide on what to log and keep goes into retention and masking.
Edge cases that break naive checks
The lookups are simple. The entities they describe are not always. Plan for these cases before the first odd vendor arrives.
Proprietors have two names. A sole proprietor's PAN carries the individual's name, while the GST registration may show the same person as legal name and the shop's brand as trade name. A name comparison between the PAN and the tradeName will fail while the comparison with legalName passes. Compare against the right field.
Individuals change names. A person's name on record may differ from the name on a current bank account or invoice, for reasons that range from marriage to spelling conventions. Initials, order of names and missing middle names produce score drops without any fraud. This is where a score and a threshold are better than an equality test, and where a human review lane is cheaper than a hard reject.
Companies convert and rename. A private company that becomes public, or a company that moves its registered office to another state, can end up with a different CIN than the one on its older letterhead. A renamed company may hold a GSTIN whose legal name has not caught up, or the other way round. When the CIN lookup returns not found for a number the vendor swears is theirs, ask for the latest certificate of incorporation before assuming fraud.
Many GSTINs, one vendor. A national vendor can quote any of its registrations depending on which office ships. All should embed the same PAN. If you accept only one GSTIN per vendor, your invoicing will eventually reject a legitimate document.
Not everyone has a GSTIN. Small businesses below the registration threshold, certain exempt suppliers and many service providers legitimately have none. Whether you may buy from an unregistered supplier is a tax and policy question for your finance team; the verification API cannot answer it.
Foreign counterparties. A foreign vendor has none of the three. Do not try to force a PAN field to accept a placeholder. Route non-resident counterparties to a different process.
Government and public bodies. Their PAN has fourth letter G or L, and they may not hold a CIN at all. A rule that insists on a CIN for every non-individual will block them.
A decision flow you can paste into a runbook
Written as plain steps so a compliance reviewer and an engineer can both read it.
- Collect the identifiers the entity type can hold (table above). If the applicant cannot supply one that the type must hold, stop and ask why.
- Run offline format checks. A failure here is a typo, not a verification result; show the user a correction message and do not spend a call.
- Compare the PAN's fourth letter with the claimed entity type. A mismatch goes to a clarifying question, not a rejection.
- Verify the PAN. If
validis false, stop. If the status is anything other than the plain valid state, route to review. - Verify the GSTIN, if one was supplied. Require
registrationStatusto beActiveand the embedded PAN to equal the verified PAN. - Verify the CIN, if the entity is a company. Compare the company name and look for the applicant among the directors.
- Compare the three names after normalisation. Anything below your threshold goes to review with all
referenceIdvalues attached. - Record the outcome, the date and the identifiers. Schedule a re-check for registrations, because a GSTIN that was active at onboarding can be cancelled later.
Mismatch patterns and what they usually mean
| What you see | Common benign cause | Common risky cause | What to do |
|---|---|---|---|
| PAN name differs from GSTIN legal name | Rename, punctuation, "Pvt" versus "Private" | Registration belongs to a different entity | Normalise, then ask for the incorporation or rename certificate |
| GSTIN embeds a different PAN than the one given | Applicant gave a director's or proprietor's PAN | Applicant is using a borrowed GSTIN | Ask which PAN the registration was made under |
| CIN company name differs from GSTIN legal name | Trade name used on GSTIN, or a rename not yet propagated | Unrelated entities | Compare with the certificate of incorporation |
| PAN valid but GSTIN not active | Registration lapsed or cancelled and not reinstated | Entity trading without valid registration | Do not onboard for GST invoicing until resolved |
| Applicant not among directors | Director list at source is behind a recent appointment | Applicant has no authority | Require an authorisation letter and board resolution |
Sandbox values and what a sandbox call tells you
In sandbox the API returns sample data and never verifies a real record. The documented magic inputs give a predictable outcome: PAN SNDBX0000A is valid and SNDBX9999F is not; GSTIN 09SNDBX0000A1Z5 is valid and active and 09SNDBX9999F1Z5 is not valid and shows as cancelled; for CIN, U00000SB0000SANDBOX000001 is valid and U00000SB0000SANDBOX999999 is not. Any other input gets a stable but arbitrary outcome: the same input always gives the same answer, and roughly two in three come back valid. Every name returned is synthetic.
Two cautions. The CIN test values are not twenty-one characters long, so if your own form-side CIN pattern rejects them, test the API with a relaxed pattern in sandbox. And never treat a sandbox "valid" as evidence about a real company. Read what sandbox can and cannot test before you rely on sandbox results in a demo.
Cost and call budgeting
Each lookup is its own billable call at production rates, so the order of calls is a cost decision. The PAN lookup is ₹5 per call, GSTIN is ₹5 and CIN is ₹5 on the standard pay-as-you-go rate. Plans lower these; the pricing page has the current table. Sandbox calls never debit your wallet. A practical order: run free offline checks first, then PAN, then GSTIN only if the PAN passes, then CIN only for companies. Do not call GSTIN for an individual who has no registration, and do not call CIN for a proprietor.
Common mistakes
- Treating a GSTIN as proof of incorporation. A proprietor can have a GSTIN and no company at all.
- Comparing names with exact string equality. Normalise case, punctuation and common suffixes first, then use the score from the API where one exists.
- Storing only
valid: true. Store thereferenceId, the date and the fields you branch on, or you cannot explain a decision later. - Running the GSTIN check character in sandbox. It rejects every documented sample.
- Forgetting that one PAN has many GSTINs, and verifying only the first one a vendor mentions.
- Using a CIN from an old document without noting that the state or listing status inside it may have changed.
- Calling the endpoints from a browser. The key belongs on your server; see authentication.
Glossary
| Term | Meaning |
|---|---|
| PAN | Permanent Account Number, the ten-character income tax identifier |
| GSTIN | Fifteen-character GST registration number |
| CIN | Twenty-one-character corporate identity number from the Registrar of Companies |
| LLPIN | Identification number of a limited liability partnership |
| DIN | Director Identification Number |
| RoC | Registrar of Companies |
| Udyam | Registration for micro, small and medium enterprises |
referenceId | StampMitra's own identifier for one verification call |
What to do next
If you are building the checks, start with the quickstart to get a sandbox key, then read the endpoint references for PAN, GSTIN and CIN. For handling failures, see errors, retries and idempotency. For what to keep after each call, see what to log from a verification call. Questions on inputs that are not covered here go to support.
API reference at a glance
PAN
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).
GSTIN
POST /v1/verify/gstinRequest 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. |
gstinrequired | string | 15-character GSTIN, format 07AAAAA1111A1Z1. |
businessName | string | Business (legal or trade) name to cross-check against the registration. |
POST /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"
}POST /v1/verify/gstinGSTIN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).
Company CIN
POST /v1/verify/cinRequest 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. |
cinrequired | string | Corporate Identification Number (21 characters) or LLPIN, for example U74999DL2015PTC284009. |
POST /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"
}
]
}POST /v1/verify/cinCompany CIN: ₹5 per successful call, pay-as-you-go (lower on Growth and Scale).
Frequently asked questions
What is the difference between PAN, GSTIN and CIN?
PAN is the ten-character income tax identifier of a taxpayer, GSTIN is the fifteen-character number of a single GST registration, and CIN is the twenty-one-character number of a company registered with the Registrar of Companies. They come from different authorities and answer different questions.
Does a GSTIN contain the PAN?
Yes. Characters 3 to 12 of a GSTIN are the PAN of the registered taxpayer. That is why you can compare a GSTIN with a PAN you already verified without making another call, and why one PAN can appear inside several GSTINs.
Can I get a company's PAN from its CIN?
No. A CIN encodes listing status, industry, state, year and ownership type, and it does not contain the PAN. Ask the company for its PAN, verify it, and compare the registered name with the company name returned by the CIN lookup.
What does the fourth letter of a PAN mean?
It shows the holder type: P individual, C company, H Hindu Undivided Family, F firm including LLP, A association of persons, B body of individuals, T trust, L local authority, J artificial juridical person and G government. Use it as a consistency check against the claimed entity type.
Why can one business have several GSTINs?
GST registration is per state, so a business operating in three states holds three GSTINs that all embed the same PAN. A business can also hold more than one registration in one state, which the thirteenth character of the GSTIN counts.
Does a valid PAN prove the applicant is the PAN holder?
No. A PAN lookup confirms that a number exists with a status and a name on record. It cannot show that the person typing the number owns it. If you need that assurance you need a separate ownership step, and StampMitra does not offer Aadhaar-based verification.
Which identifiers should I ask a private limited company for?
Ask for the company PAN, the GSTIN and the CIN. They are three independent records that you can cross-check, and the CIN lookup also returns the directors, which you compare with the person who is applying.
Does an LLP have a CIN?
An LLP is identified by an LLPIN, written as three letters, a hyphen and four digits, rather than a company CIN. Check the CIN reference for the inputs accepted on your account before sending an LLPIN to that endpoint.
Should I validate the GSTIN check character in sandbox?
No. The sample GSTIN in the docs and the documented sandbox GSTINs do not carry a valid check character, because they are placeholders. Apply the check-character test only when you use a production key.
Which StampMitra endpoints verify these three identifiers?
PAN uses POST /v1/verify/pan, GSTIN uses POST /v1/verify/gstin and CIN uses POST /v1/verify/cin. Each needs its own scope on the API key, returns its own referenceId, and is billed as a separate call in production.
How do I reduce wasted calls when checking all three?
Run free offline checks first (format, PAN fourth letter, GSTIN state code and check character), then call PAN, then GSTIN only if the PAN passes, then CIN only for companies. Do not call GSTIN or CIN for entity types that cannot hold them.
Sources
- Income Tax Department, e-filing portal pages on PAN structure and holder categories: https://www.incometax.gov.in (confirm current rules there; reviewed against this repository's API data on 2026-10-29).
- GST portal, registration and GSTIN structure: https://www.gst.gov.in (confirm the current state code list and registration categories there, 2026).
- Ministry of Corporate Affairs, company and LLP registry information: https://www.mca.gov.in (CIN and LLPIN structure, 2026).
- StampMitra API reference data for the PAN, GSTIN and CIN endpoints, fields, errors and sample values, as in this repository on 2026-10-29.
- StampMitra sandbox test values, copied from the sandbox responder, as of 2026-10-29.