StampMitraDevelopers

गाइड

Node.js और Python से PAN वेरिफाई कैसे करें: चरण-दर-चरण गाइड

Node.js या Python से PAN वेरिफाई करने के लिए अपने सर्वर से https://api-b2b.stampmitra.in/v1/verify/pan पर POST भेजें। Header में Authorization: Bearer sk_test_... लगाएं और body में pan (ABCDE1234F) तथा वैकल्पिक name दें। जवाब में valid, panStatus और referenceId मिलता है। key को सर्वर पर ही रखें, और valid: false को error नहीं, पूरा हुआ नतीजा मानें।

लेखक: StampMitra Developer Teamअंतिम अपडेट: यह पेज English में पढ़ें

Node.js या Python से PAN वेरिफाई करना असल में एक ही HTTPS कॉल है: आप https://api-b2b.stampmitra.in/v1/verify/pan पर POST भेजते हैं, header में Authorization: Bearer sk_test_... लगाते हैं और body में {"pan": "ABCDE1234F"} देते हैं। जवाब में JSON आता है, जिसमें valid, registeredName, panStatus और StampMitra का अपना referenceId होता है। बाक़ी सारी मेहनत इस कॉल के आसपास की है: key को सुरक्षित रखना, input जांचना, errors संभालना और नतीजा सही तरह से सेव करना।

इस गाइड में हम एक छोटा-सा असली प्रोजेक्ट बनाएंगे। मान लीजिए "तेज़डिलीवर" नाम की एक (काल्पनिक) कंपनी अपने डिलीवरी पार्टनर्स को जोड़ते समय PAN जांचना चाहती है। पहले हम Python में पूरी सर्विस बनाएंगे (requests और FastAPI के साथ), फिर वही सर्विस Node.js में (Express के साथ) दोहराएंगे, और आख़िर में टेस्टिंग, production और troubleshooting देखेंगे। हर कोड नमूना StampMitra की PAN API documentation में दिए गए असली request और response के आकार से मेल खाता है।

ध्यान रहे कि StampMitra एक निजी LegalTech प्लेटफ़ॉर्म है, जिसे Bani Global Industries LLP चलाती है। यह कोई सरकारी संस्था या law firm नहीं है, और इस गाइड की बातें सामान्य जानकारी हैं, क़ानूनी सलाह नहीं। अगर आपके use case में नियम या अनुपालन का सवाल हो, तो किसी योग्य सलाहकार से पूछें।

मुख्य बातें

  • Endpoint है POST /v1/verify/pan, और इसके लिए API key पर verification.pan.read scope होना ज़रूरी है।
  • Body में pan अनिवार्य है (format ABCDE1234F), और name वैकल्पिक है (अधिकतम 256 characters)।
  • Authentication के लिए Authorization: Bearer <key> या x-api-key: <key> में से कोई एक header चलता है। Sandbox key sk_test_ से शुरू होती है, production key sk_live_ से।
  • "valid": false भी एक पूरा हो चुका verification है, कोई error नहीं। इसे अपने UI में "PAN मान्य नहीं" की तरह दिखाएं।
  • गलत format वाला PAN पहले ही 400 के साथ लौट जाता है, इसलिए अपने सर्वर पर भी format जांचें।
  • हर जवाब में referenceId (जैसे SMV-20260927-8F42K7) आता है। इसे अपने रिकॉर्ड के साथ सेव करें, पर यह idempotency key नहीं है।
  • Error हमेशा { "error": { "code", "message", "requestId" } } आकार में आता है। requestId को log करें, ताकि support से बात करते समय काम आए।
  • API key कभी browser, मोबाइल ऐप या GitHub में न जाए। StampMitra को सिर्फ़ अपने backend से कॉल करें।
  • Sandbox में test कॉल wallet से पैसे नहीं काटतीं। Production में हर सफल कॉल की क़ीमत ₹5 है।

हम क्या बना रहे हैं और ऐसा क्यों?

तेज़डिलीवर के ऑपरेशंस वाले साथी रोज़ सैकड़ों नए डिलीवरी पार्टनर जोड़ते हैं। हर पार्टनर से PAN लिया जाता है, क्योंकि भुगतान और TDS जैसे हिसाब के लिए सही PAN चाहिए। अभी टीम PAN को आंख से देखकर या स्प्रेडशीट में मिलाकर जांचती है। नतीजा यह कि टाइपिंग की गलतियां बाद में पकड़ में आती हैं, जब पेमेंट अटक चुका होता है। हमारा काम है एक छोटी सर्विस बनाना, जो अलग-अलग ऐप्स के लिए एक ही जगह PAN जांच दे।

सर्विस का ढांचा सीधा रखेंगे। बाहर से आपकी अपनी ऐप (वेब फ़ॉर्म, ऑपरेशंस डैशबोर्ड या मोबाइल ऐप) हमारे backend के /kyc/pan route को कॉल करेगी। यह route अपनी तरफ़ से format जांचेगा, फिर StampMitra के /v1/verify/pan को कॉल करेगा, और जवाब को साफ़-सुथरे रूप में लौटाएगा। इस बीच की परत की ज़रूरत इसलिए है कि API key सिर्फ़ सर्वर पर रहे। ध्यान से देखें तो यह वही नियम है जो StampMitra के Build with AI पेज के prompts में भी दिया गया है: "Call StampMitra only from server-side code".

इस प्रोजेक्ट में क्या-क्या आएगा

पूरा walkthrough इन चरणों में चलेगा, और हर चरण पर चलने लायक कोड मिलेगा:

  1. एक sandbox key बनाकर .env में रखना और cURL से पहली कॉल करना।
  2. Python में एक छोटा client function, फिर FastAPI route।
  3. वही दोनों चीज़ें Node.js (Express) में।
  4. नाम मिलान, errors, retry और timeout का सही व्यवहार।
  5. pytest और node:test से बिना असली कॉल किए टेस्ट।
  6. production में जाने से पहले की checklist।

अगर आप सिर्फ़ PAN API का सामान्य परिचय (कौन से fields, किस काम के लिए) चाहते हैं, तो पहले भारत के लिए PAN verification API गाइड पढ़ लें। यह लेख उसके आगे का है: यहां ध्यान "कोड कैसे लिखें" पर है।

शुरू करने से पहले: API key, scope और .env फ़ाइल

सबसे पहले एक sandbox खाता बनाइए। साइन-अप में email, मोबाइल नंबर और password लगते हैं, और StampMitra के quickstart के मुताबिक़ इसमें credit card या manual approval नहीं चाहिए। आपको तुरंत sk_test_ से शुरू होने वाली key मिलती है। ध्यान दें कि raw key सिर्फ़ एक बार दिखाई जाती है, इसलिए उसे उसी वक़्त किसी सुरक्षित जगह रख लें। चरण-दर-चरण तरीक़ा quickstart में है और authentication का पूरा मॉडल API key docs में।

Key कैसी होती है और कहां रखनी है

  • sk_test_... sandbox के लिए है, sk_live_... production के लिए। production key तभी मिलती है जब आपका organization KYC से approved हो (auth docs देखें)।
  • हर key पर scopes लगे होते हैं। PAN के लिए आपकी key में verification.pan.read होना चाहिए। अगर key वैध है पर scope नहीं है, तो 403 FORBIDDEN मिलेगा।
  • संवेदनशील scopes पर IP allowlist भी लग सकता है। production में अपने सर्वर के fixed IP पर key को बांध देना एक अच्छी आदत है।

Key को अपने कोड में न लिखें। प्रोजेक्ट की जड़ में .env फ़ाइल बनाइए और .gitignore में उसे जोड़ दीजिए। StampMitra के prompts में environment variable का सुझाया नाम STAMPMITRA_API_KEY है, तो हम भी वही रखेंगे:

cURL
# .env  (इसे git में कभी commit न करें)
STAMPMITRA_API_KEY=sk_test_xxxxxxxxxxxx
cURL
# .gitignore
.env
.env.local

एक छोटी बात: .env.example नाम की दूसरी फ़ाइल बनाइए, जिसमें असली key की जगह placeholder हो। इससे नए साथी को पता रहता है कि कौन-सा variable चाहिए, और असली key repository में नहीं जाती।

cURL से पहली कॉल और response को पढ़ना

कोई भी भाषा चुनने से पहले टर्मिनल से कॉल करके देखिए। इससे आप भाषा और library की दिक़्क़तों को API की दिक़्क़तों से अलग रख पाते हैं।

cURL
export STAMPMITRA_API_KEY=sk_test_xxxxxxxxxxxx

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"}'

डॉक्स में दिए नमूने के अनुसार जवाब ऐसा दिखता है:

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"
}

यह नमूना है, इसलिए असली जवाब में मान अलग होंगे। यह भी याद रखिए कि sample का PAN ABCDE1234F सिर्फ़ दस्तावेज़ों का उदाहरण है; किसी असली व्यक्ति का PAN अपने सैंपल या टेस्ट में न डालें।

हर field का मतलब

Fieldक्या बताता हैआपके कोड में कैसे इस्तेमाल करें
referenceIdStampMitra का अपना transaction id, format SMV-YYYYMMDD-XXXXXXXअपने रिकॉर्ड में audit के लिए सेव करें। यह idempotency key नहीं है।
panजो PAN आपने भेजाभेजे गए मान से मिलाकर देख सकते हैं
typePAN धारक का प्रकार, जैसे Individualअगर आपको सिर्फ़ व्यक्तिगत PAN चाहिए, तो यहां जांचें
validPAN मान्य है या नहीं (boolean)मुख्य निर्णय इसी से लें
registeredNameरिकॉर्ड में दर्ज नामस्क्रीन पर दिखाने या अपने डेटा से मिलाने के लिए
panStatus, panStatusCode, panStatusDescriptionPAN की स्थिति का विवरणरिपोर्ट में दिखाएं; सिर्फ़ valid पर निर्भर न रहें
nameMatchResult, nameMatchScorename भेजने पर नाम मिलान का नतीजाnameMatchScore एक string है, "100" जैसा, इसलिए गणना से पहले number में बदलें
aadhaarSeedingStatusAadhaar लिंक की स्थितिजैसा मिले वैसा दर्ज करें, अनुमान न लगाएं
lastUpdatedAtरिकॉर्ड कब अपडेट हुआजानकारी की ताज़गी बताने के लिए
messageइंसानों के पढ़ने लायक़ संदेशसिर्फ़ लॉग या डीबग के लिए, इस पर if न लगाएं

अंतिम पंक्ति पर ज़ोर: message की भाषा या शब्द बदल सकते हैं। अपना तर्क हमेशा valid, panStatus और nameMatchResult जैसे structured fields पर रखिए।

Python या Node.js: अपनी टीम के लिए कौन-सी भाषा चुनें

यह सवाल अक्सर तकनीकी से ज़्यादा टीम का होता है। StampMitra की API दोनों भाषाओं से एक जैसी चलती है, क्योंकि आख़िर में यह एक साधारण HTTPS और JSON वाली कॉल है। इसलिए चुनाव इस आधार पर कीजिए कि आपकी बाक़ी टीम का ज़्यादातर कोड किस में है और रात को पेजर आने पर कौन उसे ठीक कर पाएगा। अगर आपका डेटा-वाला काम, स्क्रिप्ट या बैच जॉब Python में पहले से चलते हैं, तो PAN की जांच भी वहीं रखिए। अगर आपकी वेब ऐप या API गेटवे Node.js में है, तो वहीं जोड़ना कम झंझट का है। दो भाषाओं में एक ही तर्क दोबारा लिखने से बचिए, वरना एक जगह का सुधार दूसरी जगह छूट जाता है। आपके पास सिर्फ़ एक सर्विस होनी चाहिए जो PAN की जांच करे और बाक़ी सब उसी को कॉल करें। इसी वजह से हमने ऊपर /kyc/pan को एक अलग छोटे route की तरह बनाया है।

Python में पहला client: requests के साथ

अब Python की बारी। पहले एक छोटा, अकेला function लिखते हैं, जिसे आप किसी भी स्क्रिप्ट या सर्वर से बुला सकें। Python 3.9 या उससे नया और requests library काफ़ी है:

cURL
python -m venv .venv
source .venv/bin/activate
pip install requests python-dotenv
Python
# stampmitra_pan.py
import os
import requests
from dotenv import load_dotenv

load_dotenv()  # reads .env in local development

BASE_URL = "https://api-b2b.stampmitra.in"


def verify_pan(pan: str, name: str | None = None, timeout: float = 15.0) -> dict:
    api_key = os.environ["STAMPMITRA_API_KEY"]  # fail loudly if missing
    payload = {"pan": pan}
    if name:
        payload["name"] = name  # optional, max 256 chars

    resp = requests.post(
        f"{BASE_URL}/v1/verify/pan",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=timeout,
    )
    resp.raise_for_status()
    return resp.json()


if __name__ == "__main__":
    result = verify_pan("ABCDE1234F", "Rahul Kumar")
    print(result["referenceId"], result["valid"], result.get("nameMatchResult"))

यहां तीन बातें ग़ौर करने लायक़ हैं। पहली, os.environ["STAMPMITRA_API_KEY"] जान-बूझकर KeyError देता है अगर key न हो, ताकि चुपचाप खाली header भेजकर 401 खाने से बेहतर हो कि प्रोग्राम शुरू में ही रुक जाए। दूसरी, timeout हमेशा दीजिए: requests में default timeout नहीं होता, और बिना timeout के एक अटकी हुई कॉल आपका पूरा worker रोक सकती है। तीसरी, name तभी भेजा जाता है जब वह मौजूद हो, क्योंकि API में यह वैकल्पिक है।

raise_for_status पर्याप्त क्यों नहीं है

raise_for_status() 4xx और 5xx पर exception देता है, पर यह आपको error का code और requestId नहीं देता, जो body में होते हैं। अगले हिस्से में हम इसे सुधारेंगे। इसी तरह valid: false वाला जवाब HTTP 200 पर आता है, इसलिए वह exception बनेगा ही नहीं। उसे आपके कोड को अलग से संभालना होगा, और यही फ़र्क़ शुरुआती लोगों को सबसे ज़्यादा उलझाता है।

Python में FastAPI route, validation और errors

अब इसे एक सर्वर बनाते हैं। हम FastAPI और Pydantic इस्तेमाल करेंगे, ताकि format की जांच अपनी तरफ़ से हो जाए। डॉक्स के मुताबिक़ PAN का format ABCDE1234F है, यानी पांच अक्षर, चार अंक, एक अक्षर। इसके लिए नीचे का regex काफ़ी है:

cURL
pip install fastapi uvicorn
Python
# app.py
import re
from typing import Optional

import requests
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, field_validator

from stampmitra_pan import verify_pan

app = FastAPI()
PAN_RE = re.compile(r"^[A-Z]{5}[0-9]{4}[A-Z]$")


class PanRequest(BaseModel):
    pan: str
    name: Optional[str] = Field(default=None, max_length=256)

    @field_validator("pan")
    @classmethod
    def normalise_and_check(cls, v: str) -> str:
        v = v.strip().upper()
        if not PAN_RE.match(v):
            raise ValueError("PAN must look like ABCDE1234F")
        return v


@app.post("/kyc/pan")
def kyc_pan(body: PanRequest):
    try:
        data = verify_pan(body.pan, body.name)
    except requests.Timeout:
        raise HTTPException(status_code=504, detail="Verification timed out, please retry")
    except requests.HTTPError as exc:
        err = {}
        try:
            err = exc.response.json().get("error", {})
        except ValueError:
            pass
        status = exc.response.status_code
        # log the requestId, never the API key
        print("stampmitra_error", status, err.get("code"), err.get("requestId"))
        if status == 400:
            raise HTTPException(status_code=400, detail="PAN format galat hai")
        if status == 429:
            raise HTTPException(status_code=429, detail="Thodi der baad koshish karein")
        # 401, 402, 403 are OUR configuration problems, not the user's
        raise HTTPException(status_code=502, detail="Verification service unavailable")

    return {
        "referenceId": data["referenceId"],
        "valid": data["valid"],
        "registeredName": data.get("registeredName"),
        "panStatus": data.get("panStatus"),
        "nameMatchResult": data.get("nameMatchResult"),
    }

चलाने के लिए uvicorn app:app --reload और फिर:

cURL
curl -X POST http://localhost:8000/kyc/pan \
  -H "Content-Type: application/json" \
  -d '{"pan": "abcde1234f", "name": "Rahul Kumar"}'

यह कोड कौन से फ़ैसले लेता है

  • छोटे अक्षरों वाला PAN पहले upper() से बड़े अक्षरों में बदल दिया जाता है, ताकि यूज़र की टाइपिंग की वजह से बेवजह 400 न आए।
  • Format की जांच पहले अपनी तरफ़ होती है। इससे हर गलत टाइपिंग पर API कॉल नहीं जाती, पर सर्वर की जांच फिर भी असली गारंटी है, क्योंकि StampMitra भी 400 देता है।
  • 401, 402 और 403 यूज़र की गलती नहीं हैं। ये आपके key, scope या wallet की समस्याएं हैं। यूज़र को "सेवा उपलब्ध नहीं है" जैसा सामान्य संदेश दीजिए, और अपनी टीम को अलर्ट कीजिए।
  • यहां केवल जवाब के चुने हुए fields लौटाए जा रहे हैं। पूरा upstream जवाब आगे भेजना ज़रूरी नहीं है, और कम डेटा आगे भेजना बेहतर आदत है।

ध्यान रहे कि ऊपर का valid: false वाला मामला यहां अपने आप सही चलता है: जवाब में "valid": false आएगा और ऐप उसे दिखाएगी। नीचे वाले हिस्से में हम देखेंगे कि उस पर आगे क्या फ़ैसला लें।

यूज़र को क्या संदेश दिखाएं

तकनीकी रूप से सही कोड भी तब फ़ेल हो जाता है जब स्क्रीन पर संदेश उलझा हुआ हो। हर नतीजे के लिए ऐसा वाक्य सोचिए जिसे पार्टनर बिना डरे पढ़ सके। जब PAN मान्य न मिले, तो लिखिए कि "यह PAN मान्य नहीं मिला, कृपया कार्ड देखकर दोबारा भरें", न कि "verification failed"। जब सेवा अस्थायी रूप से न चले (502 या timeout), तो लिखिए कि "अभी जांच नहीं हो पाई, थोड़ी देर बाद फिर कोशिश करें"। और ध्यान रहे, यूज़र को कभी "401" या "scope missing" जैसी भीतरी बातें न दिखाएं, क्योंकि वे आपकी सेटिंग की समस्या हैं और उन्हें सिर्फ़ आपकी टीम को दिखना चाहिए। इसी तरह नाम का मिलान कमज़ोर होने पर यूज़र को "आप ग़लत हैं" जैसा आरोप न लगाइए। बस इतना कहिए कि "नाम का मिलान नहीं हो पाया, हमारी टीम इसे देखेगी"। इससे असली लोग, जिनका नाम सिर्फ़ अलग तरीक़े से लिखा है, नाराज़ होकर वापस नहीं जाते।

Node.js में वही सर्विस: Express के साथ

Python वाला ढांचा समझ आ गया है, तो Node.js में काम और छोटा है, क्योंकि Node 18 या उससे नए संस्करण में fetch पहले से मौजूद है और अलग HTTP library नहीं चाहिए। हम Express और dotenv लेंगे:

cURL
npm init -y
npm install express dotenv
JavaScript
// stampmitra-pan.js  (CommonJS)
require("dotenv").config();

const BASE_URL = "https://api-b2b.stampmitra.in";

class StampMitraError extends Error {
  constructor(status, code, message, requestId) {
    super(message);
    this.name = "StampMitraError";
    this.status = status;
    this.code = code;
    this.requestId = requestId;
  }
}

async function verifyPan(pan, name) {
  const apiKey = process.env.STAMPMITRA_API_KEY;
  if (!apiKey) throw new Error("STAMPMITRA_API_KEY is not set");

  const payload = { pan };
  if (name) payload.name = name; // optional, max 256 chars

  const res = await fetch(`${BASE_URL}/v1/verify/pan`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(15000), // 15s timeout
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    const e = body.error || {};
    throw new StampMitraError(res.status, e.code, e.message, e.requestId);
  }
  return body;
}

module.exports = { verifyPan, StampMitraError };
JavaScript
// server.js
const express = require("express");
const { verifyPan, StampMitraError } = require("./stampmitra-pan");

const app = express();
app.use(express.json());

const PAN_RE = /^[A-Z]{5}[0-9]{4}[A-Z]$/;

app.post("/kyc/pan", async (req, res) => {
  const pan = String(req.body.pan || "").trim().toUpperCase();
  const name = req.body.name ? String(req.body.name).slice(0, 256) : undefined;

  if (!PAN_RE.test(pan)) {
    return res.status(400).json({ message: "PAN format galat hai (ABCDE1234F)" });
  }

  try {
    const data = await verifyPan(pan, name);
    return res.json({
      referenceId: data.referenceId,
      valid: data.valid,
      registeredName: data.registeredName,
      panStatus: data.panStatus,
      nameMatchResult: data.nameMatchResult,
    });
  } catch (err) {
    if (err.name === "TimeoutError") {
      return res.status(504).json({ message: "Verification timed out, please retry" });
    }
    if (err instanceof StampMitraError) {
      console.error("stampmitra_error", err.status, err.code, err.requestId);
      if (err.status === 400) return res.status(400).json({ message: "PAN format galat hai" });
      if (err.status === 429) return res.status(429).json({ message: "Thodi der baad koshish karein" });
      return res.status(502).json({ message: "Verification service unavailable" });
    }
    console.error("unexpected", err.message);
    return res.status(500).json({ message: "Internal error" });
  }
});

app.listen(3000, () => console.log("listening on :3000"));

Python और Node.js के बीच असली फ़र्क़

दोनों कोड एक ही काम करते हैं, पर कुछ बारीकियां अलग हैं, जो गलती कराती हैं। नीचे की तालिका में सबसे ज़रूरी बातें हैं:

मुद्दाPython (requests)Node.js (fetch)
Timeouttimeout= अपने आप नहीं लगता, खुद देना पड़ता हैAbortSignal.timeout(ms) खुद लगाना पड़ता है
HTTP 4xx/5xx परraise_for_status() exception देता हैfetch exception नहीं देता, res.ok खुद जांचना पड़ता है
JSON bodyjson=payload header और serialization संभालता हैJSON.stringify और Content-Type खुद लगाएं
Error bodyexc.response.json()await res.json()
Timeout का errorrequests.TimeoutTimeoutError (name से पहचानें)

Node वाली सबसे आम गलती यह है कि लोग मान लेते हैं fetch 401 या 400 पर भी fail होगा। ऐसा नहीं है, वह तभी fail होता है जब नेटवर्क ही न मिले। इसलिए ऊपर res.ok की जांच ज़रूरी है।

Timeout, retry और 429: कौन से errors दोबारा आज़माएं

हर error पर retry करना ग़लत है। StampMitra के error कोड और उनका सही बर्ताव ऐसा बनता है। सभी status डॉक्स में दिए गए error उदाहरणों से लिए गए हैं:

HTTP statuserror.codeमतलबRetry करें?
400BAD_REQUESTInput गलत, जैसे "pan must match the format ABCDE1234F"नहीं, पहले input ठीक करें
401UNAUTHORIZEDKey गायब या गलतनहीं, key और header जांचें
403FORBIDDENKey सही, पर scope नहींनहीं, key का scope बदलवाएं
402PAYMENT_REQUIREDWallet में पर्याप्त balance नहींनहीं, balance भरें
429TOO_MANY_REQUESTSRate limit (डॉक्स का नमूना: 60 requests प्रति 60 seconds, इस endpoint के लिए)हां, कुछ देर रुककर
502INTERNAL_ERRORUpstream verification सेवा तक पहुंच नहीं बनीहां, सीमित बार और अंतराल के साथ

सिर्फ़ 429, 502 और नेटवर्क timeout पर दोबारा कोशिश कीजिए, और वह भी गिनी-चुनी बार। एक सरल पैटर्न है: पहले 1 सेकंड, फिर 2, फिर 4 सेकंड का इंतज़ार, और तीन कोशिशों के बाद हार मान लेना। नीचे Python में इसका नमूना है:

Python
import time
import requests
from stampmitra_pan import verify_pan

RETRYABLE = {429, 502}


def verify_pan_with_retry(pan, name=None, attempts=3):
    delay = 1.0
    for attempt in range(1, attempts + 1):
        try:
            return verify_pan(pan, name)
        except requests.Timeout:
            if attempt == attempts:
                raise
        except requests.HTTPError as exc:
            if exc.response.status_code not in RETRYABLE or attempt == attempts:
                raise
        time.sleep(delay)
        delay *= 2

और Node.js में वही तर्क:

JavaScript
const { verifyPan, StampMitraError } = require("./stampmitra-pan");

const RETRYABLE = new Set([429, 502]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function verifyPanWithRetry(pan, name, attempts = 3) {
  let delay = 1000;
  for (let i = 1; i <= attempts; i++) {
    try {
      return await verifyPan(pan, name);
    } catch (err) {
      const retryable =
        err.name === "TimeoutError" ||
        (err instanceof StampMitraError && RETRYABLE.has(err.status));
      if (!retryable || i === attempts) throw err;
      await sleep(delay);
      delay *= 2;
    }
  }
}

Retry में सबसे बड़ा जोखिम

डॉक्स साफ़ कहते हैं कि referenceId एक idempotency key नहीं है, और सफल verification पर ही usage दर्ज होता है तथा wallet से पैसा कटता है (priced services में)। इसका मतलब: अगर timeout के बाद आप वही कॉल दोबारा भेजें, तो हो सकता है पहली कॉल असल में पूरी हो चुकी हो और दोनों बार शुल्क लगे। इसलिए retry को कम रखें, और अपनी तरफ़ एक छोटा cache रखें (जैसे कुछ मिनट के लिए PAN और नाम का जोड़ा) ताकि यूज़र के बार-बार बटन दबाने पर वही कॉल न दोहराई जाए। अगर आपको त्रुटियों को और गहराई से संभालना है, तो API errors और handling गाइड में इसका विस्तार है।

Local में चलाने का एक छोटा अभ्यास

इस लेख के कोड को समझने का सबसे अच्छा तरीक़ा है उसे खुद चलाना। पहले एक खाली फ़ोल्डर बनाइए और उसमें .env रखिए। फिर Python वाली स्क्रिप्ट सीधे चलाइए और देखिए कि referenceId छपता है या नहीं। इसके बाद जान-बूझकर .env से key हटाकर चलाइए, और देखिए कि प्रोग्राम शुरू में ही साफ़ संदेश देकर रुक जाता है। फिर PAN की जगह कोई छोटी स्ट्रिंग डालकर 400 का रास्ता आज़माइए। यही अभ्यास Node.js वाली सर्विस के साथ दोहराइए। हर बार आप एक नया हालात देखेंगे: सफलता, key गायब, format गलत। जब आप तीनों हालात अपनी आंखों से देख लेंगे, तब production में कोई error आने पर आप घबराएंगे नहीं, क्योंकि आपने उसका चेहरा पहले देखा हुआ होगा।

`valid: false` और नाम मिलान: असली फ़ैसला कैसे लें

अब तक हमने कॉल को चलाना सीखा। असली व्यवसाय का फ़ैसला इसके बाद आता है। डॉक्स का नियम साफ़ है: सकारात्मक और नकारात्मक दोनों नतीजे (valid: true और valid: false) पूरे हो चुके, और billable, verification हैं। इसलिए valid: false आने पर आपको दोबारा कोशिश नहीं करनी चाहिए, बल्कि यूज़र को बताना चाहिए कि दिया गया PAN मान्य नहीं मिला।

तीन स्थितियां, तीन व्यवहार

सोचिए तेज़डिलीवर का ऑपरेशंस साथी "सुनील" एक नया पार्टनर जोड़ रहा है। तीन अलग नतीजे आ सकते हैं:

  1. valid: true और nameMatchResult मेल खाता है: पार्टनर का रिकॉर्ड आगे बढ़ा दीजिए और referenceId सेव कीजिए।
  2. valid: true, पर नाम का मिलान कमज़ोर या अलग है: इसे अपने आप reject न करें। इसे "मैन्युअल समीक्षा" की कतार में डालें, क्योंकि नाम अक्सर संक्षिप्त (जैसे "R. Kumar") या अलग क्रम में लिखा जाता है।
  3. valid: false: यूज़र से PAN दोबारा भरवाएं। यह error नहीं है, इसलिए लॉग में "failure" न गिनें।

अपने कोड में नाम के लिए कोई मनमाना if score > 80 न रखें, जब तक आपने असली डेटा पर उसे परखा न हो। nameMatchScore एक string में आता है और डॉक्स में उसके स्वीकार्य मान की कोई सीमा नहीं बताई गई, इसलिए अपनी सीमा खुद तय करें और उसे कॉन्फ़िगरेशन में रखें, कोड में नहीं। एक छोटा हेल्पर:

Python
def decide(result: dict, review_threshold: int = 90) -> str:
    if not result.get("valid"):
        return "reject_invalid_pan"
    if "name" not in result and result.get("nameMatchResult") is None:
        return "accept_without_name_check"
    score = int(result.get("nameMatchScore") or 0)
    if result.get("nameMatchResult") == "MATCH" and score >= review_threshold:
        return "accept"
    return "manual_review"

यहां review_threshold एक कॉन्फ़िगरेशन मान है जिसे आपकी टीम तय करती है। यह StampMitra का कोई आधिकारिक आंकड़ा नहीं है।

कब PAN वेरिफाई करें और कब नहीं: एक फ़ैसला-मार्गदर्शिका

हर यूज़र के लिए हर बार API कॉल चलाना ज़रूरी भी नहीं और सस्ता भी नहीं। production में हर सफल कॉल की क़ीमत ₹5 है, तो कॉल तभी कीजिए जब उसका कारण हो।

कब करें

  • जब आप पैसे भेजने या TDS जैसे हिसाब के लिए PAN पर निर्भर हैं, और गलत PAN बाद में समस्या बनेगा।
  • Onboarding के समय, एक बार, और नतीजे को referenceId के साथ सेव करके।
  • जब यूज़र अपना PAN बदलता है।
  • जब आप नाम का मिलान भी करना चाहते हैं, तो name भेजकर, ताकि एक ही कॉल से दोनों जवाब मिलें।

कब न करें

  • हर लॉगिन पर या हर पेज लोड पर। एक बार verified PAN को अपने डेटाबेस में रखकर इस्तेमाल करें।
  • जब PAN का format ही साफ़ गलत है। पहले अपनी तरफ़ regex से जांचिए, API पर तभी जाइए जब format सही हो।
  • सिर्फ़ "टेस्ट" के लिए production key से, क्योंकि वह असली wallet से कटती है। टेस्ट sandbox में कीजिए।
  • जब आपको किसी और चीज़ की ज़रूरत है। अगर आपको कंपनी की जानकारी चाहिए, तो PAN की जगह GSTIN या PAN से GSTIN जैसे दूसरे endpoints हैं, और वह रास्ता GSTIN verification गाइड में समझाया गया है।

एक आसान कसौटी: "क्या मैं इस PAN पर पहले ही कॉल कर चुका हूं, और क्या वह नतीजा अब भी मेरे काम का है?" अगर जवाब हां है, तो दोबारा कॉल मत कीजिए।

सुरक्षा और निजी डेटा: API key, लॉग और सहमति

PAN एक निजी पहचान-संख्या है, इसलिए इसे वैसे ही संभालिए जैसे किसी की निजी जानकारी को। यहां का हिस्सा सामान्य अच्छी आदतें बताता है और क़ानूनी सलाह नहीं है। डेटा सुरक्षा पर भारत का Digital Personal Data Protection Act, 2023 लागू होता है, पर इसकी धाराओं और आपके दायित्वों की व्याख्या के लिए अपने क़ानूनी सलाहकार से बात कीजिए। इस लेख में मैंने उसकी किसी धारा का हवाला जान-बूझकर नहीं दिया है।

Key की सुरक्षा

  • Key केवल सर्वर के environment variable या किसी secrets manager में रखें। browser, मोबाइल ऐप, या सार्वजनिक GitHub repository में कभी नहीं।
  • अगर key लीक हो गई हो, तो उसे तुरंत बदलें। StampMitra की API सुरक्षा नीति और स्वीकार्य उपयोग नीति पढ़ने लायक़ हैं।
  • Production key पर जहां संभव हो IP allowlist लगाएं।
  • लॉग में Authorization header कभी न छापें। HTTP client की debug logging चालू करने से पहले सोच लें कि वह header छाप सकती है।

टीम में key कैसे बांटें

छोटी टीमों में अक्सर एक ही key सबके पास घूमती रहती है: Slack में, किसी स्प्रेडशीट में, या किसी के लैपटॉप की फ़ाइल में। इससे बचने का सीधा तरीक़ा है कि key सिर्फ़ उस सर्वर के secrets में रहे जो StampMitra को कॉल करता है। डेवलपर अपने लैपटॉप पर सिर्फ़ sandbox key इस्तेमाल करें, और production key किसी इंसान की पहुंच में ही न हो। जब कोई साथी टीम छोड़े, तो उसके पास रही हर key बदलना एक साधारण प्रक्रिया होनी चाहिए, कोई बड़ा संकट नहीं। इसके लिए ऐसा ढांचा बनाइए जहां key का मान एक ही जगह (environment variable) से पढ़ा जाए, ताकि बदलाव में कोड न छूना पड़े। और कोड रिव्यू में एक आसान जांच जोड़ लीजिए: क्या इस बदलाव में sk_ से शुरू होने वाली कोई स्ट्रिंग लिखी हुई है?

PAN के साथ क्या करें और क्या नहीं

  • उद्देश्य तय रखें: PAN सिर्फ़ उसी काम के लिए लें और जांचें जिसके लिए आपने यूज़र को बताया था।
  • सहमति पहले: फ़ॉर्म में साफ़ लिखें कि PAN क्यों मांगा जा रहा है और उसकी जांच बाहरी सेवा से होगी।
  • कम से कम स्टोर करें: पूरे जवाब की जगह ज़रूरी fields (referenceId, valid, panStatus, नतीजे की तारीख़) रखें। fatherName जैसे fields तभी रखें जब आपको सच में चाहिए।
  • लॉग में PAN मास्क करें: ABCDE1234F की जगह ABCDE****F जैसा रूप छापें।
  • टेस्ट में असली PAN नहीं: डॉक्स का नमूना PAN या अपने बनाए नकली मान इस्तेमाल करें।

StampMitra का डेटा-संबंधी रुख डेवलपर प्राइवेसी नीति और डेटा प्रोसेसिंग एडेंडम में लिखा है। इनमें से दोनों अंग्रेज़ी में हैं, पर अपने अनुपालन के निर्णय से पहले इन्हें पढ़ लेना अच्छा रहेगा।

बैच में कई PAN जांचना: CSV से onboarding

तेज़डिलीवर की असली ज़रूरत सिर्फ़ एक फ़ॉर्म नहीं है। महीने की शुरुआत में कई बार ऑपरेशंस टीम को सौ-दो सौ पार्टनर्स की CSV मिलती है, जिसमें नाम और PAN होते हैं। यहां का सवाल यह है कि इन सबको एक साथ कैसे जांचें, बिना rate limit तोड़े और बिना ग़लती से एक ही PAN पर दो बार पैसे ख़र्च किए। StampMitra के डॉक्स में कोई अलग bulk endpoint नहीं दिया गया है, इसलिए हम यही मानकर चलते हैं कि हर PAN के लिए एक अलग कॉल होगी।

कतार बनाइए, पर धीरे चलिए

सबसे आसान तरीक़ा है कि एक समय में सिर्फ़ थोड़े-से कॉल चलें। डॉक्स के नमूने में rate limit का संदेश "60 requests per 60 seconds for this endpoint" है, इसलिए अगर आप 200 PAN एक साथ दाग दें तो बड़ी संख्या में 429 मिलेंगे और retry के चक्कर में आप और देर लगाएंगे। एक सुरक्षित शुरुआत है, प्रति सेकंड एक कॉल के आसपास रहना, और अपने खाते की असली सीमा पुष्टि करके उसे बढ़ाना। यह आंकड़ा आपके खाते के हिसाब से बदल सकता है, इसलिए उसे कॉन्फ़िगरेशन में रखिए।

अगला क़दम है दोहराव हटाना। CSV में एक ही PAN दो बार आ सकता है, कभी छोटे अक्षरों में और कभी आख़िर में खाली जगह के साथ। पहले हर PAN को strip() और upper() से सामान्य कीजिए, फिर set बनाकर अनोखे PAN अलग कीजिए। इसी से आप बेवजह की कॉल और बेवजह का शुल्क दोनों बचाते हैं। जो PAN format की जांच में ही फेल हो जाएं, उन्हें API तक भेजने की ज़रूरत नहीं है, सीधे "रद्द: format ग़लत" की सूची में डाल दीजिए।

Python में एक सीधा लूप

नीचे का नमूना क्रम से चलता है, हर कॉल के बीच थोड़ा रुकता है, और नतीजे को एक सूची में जमा करता है। यह जान-बूझकर सरल रखा गया है, ताकि आप इसे अपनी ज़रूरत के हिसाब से बदल सकें:

Python
import csv, re, time
from stampmitra_pan import verify_pan

PAN_RE = re.compile(r"^[A-Z]{5}[0-9]{4}[A-Z]$")


def run_batch(path, pause=1.0):
    seen, rows = set(), []
    with open(path, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            pan = row["pan"].strip().upper()
            if pan in seen:
                continue
            seen.add(pan)
            if not PAN_RE.match(pan):
                rows.append({"pan": pan, "status": "bad_format"})
                continue
            try:
                r = verify_pan(pan, row.get("name"))
                rows.append({"pan": pan, "status": "ok", "valid": r["valid"],
                             "referenceId": r["referenceId"]})
            except Exception as exc:  # keep the batch alive
                rows.append({"pan": pan, "status": "error", "detail": str(exc)})
            time.sleep(pause)
    return rows

इस लूप में एक बात ध्यान देने वाली है: एक PAN पर error आने से पूरा बैच नहीं रुकता। हर पंक्ति का अपना नतीजा बनता है, और जो error में गए उन्हें बाद में अलग से दोबारा चलाया जा सकता है। फिर भी, ऊपर की रिपोर्ट में सीधे str(exc) रखना ठीक नहीं है अगर उसमें संवेदनशील जानकारी आ सकती हो, इसलिए असली सिस्टम में सिर्फ़ status और requestId रखिए।

Node.js में वही सोच

Node.js में भी तर्क बिल्कुल वही रहता है। फ़र्क़ बस यह है कि लूप async है और रुकने के लिए await sleep(ms) लगता है। बहुत लोग यहां Promise.all से सारे PAN एक साथ भेज देते हैं और फिर 429 की बाढ़ से हैरान होते हैं। Promise.all तब ठीक है जब आपके पास पांच-दस कॉल हों। बड़ी सूची के लिए for ... of और बीच में एक छोटा इंतज़ार ही सही है। अगर आपको समानांतर चलाना ही है, तो एक बार में दो-तीन कॉल की सीमा रखिए, और 429 मिलने पर वह सीमा अपने आप घटा दीजिए।

नतीजे को सही तरह सेव कीजिए: referenceId और audit trail

वेरिफिकेशन के बाद सबसे आम चूक है कि लोग सिर्फ़ "verified: true" का एक बूलियन सेव कर लेते हैं। छह महीने बाद जब कोई पूछता है कि "यह PAN कब और किस आधार पर मान्य माना गया था?", तो उनके पास कोई जवाब नहीं होता। StampMitra का referenceId इसी सवाल के लिए है। डॉक्स के मुताबिक़ यह StampMitra का अपना transaction id है और नकारात्मक नतीजे पर भी हमेशा मौजूद रहता है, यानी आप हर कॉल का एक ठोस निशान रख सकते हैं।

डेटाबेस में क्या रखें

एक सरल तालिका सोचिए, जिसका नाम pan_checks हो। हर पंक्ति एक कॉल का रिकॉर्ड हो। नीचे का सुझाव सिर्फ़ एक नमूना है, इसे अपनी ज़रूरत के हिसाब से बदलिए:

कॉलमक्यों चाहिए
partner_idयह जांच किस पार्टनर की थी
pan_maskedABCDE****F जैसा मास्क किया रूप, लॉग और स्क्रीन के लिए
validमुख्य नतीजा
pan_statuspanStatus का मान, जैसा मिला
name_match_resultनाम भेजा गया हो तो उसका नतीजा
reference_idStampMitra का referenceId, audit के लिए
checked_atआपकी तरफ़ का समय, ताकि पता रहे कि जांच कब हुई
checked_byकिस यूज़र या किस सिस्टम ने कॉल करवाई

इस तालिका में पूरा PAN रखना ज़रूरी है या नहीं, यह आपके अपने उद्देश्य पर निर्भर है। अगर आपको भुगतान के लिए असली PAN चाहिए, तो उसे अलग, सीमित पहुंच वाली जगह पर एन्क्रिप्ट करके रखिए, और इस audit तालिका में सिर्फ़ मास्क किया रूप। इस तरह जिसे सिर्फ़ रिपोर्ट देखनी है, उसे पूरा PAN दिखता ही नहीं।

कितने समय तक रखें

डेटा को कितने समय तक रखना है, इसका एक ही सही जवाब नहीं है। यह आपके व्यवसाय के नियमों, अनुबंधों और लागू क़ानूनों पर निर्भर है, और उसके लिए अपने सलाहकार से पूछना सही रहेगा। सामान्य सिद्धांत यही है कि जितनी ज़रूरत हो उतना ही रखिए, और समय बीतने पर मिटाने की एक तय प्रक्रिया बनाइए। यहां मैं कोई अवधि नहीं बता रहा, क्योंकि वह अवधि मैंने किसी आधिकारिक स्रोत से सत्यापित नहीं की है।

Support से बात करते समय

जब कभी कोई कॉल अजीब नतीजा दे, तो referenceId या error की स्थिति में requestId, कॉल का समय और endpoint का नाम तैयार रखिए। इन्हीं तीन चीज़ों से किसी भी तरफ़ का इंजीनियर आपकी कॉल को ढूंढ पाता है। अपनी API key, पूरा PAN या पार्टनर की निजी जानकारी mail में कभी मत चिपकाइए।

एक पूरा worked example: सुनील के तीन पार्टनर

अब तक की बातें एक असली दिन के दौरान कैसे दिखती हैं, यह एक काल्पनिक उदाहरण से देखते हैं। सुनील आज तीन नए पार्टनर जोड़ रहा है। उसका सिस्टम ऊपर बने /kyc/pan route से बात करता है।

पहला पार्टनर "अनीता" है। वह सही PAN और अपना पूरा नाम भरती है। सिस्टम format जांचकर कॉल भेजता है और StampMitra से valid: true तथा nameMatchResult: "MATCH" वाला जवाब आता है। सुनील की स्क्रीन पर हरा निशान दिखता है और referenceId रिकॉर्ड में चला जाता है। यहां कुछ और करने की ज़रूरत नहीं।

दूसरा पार्टनर "मोहन" है, जो PAN में एक अंक की जगह ग़लती से अक्षर टाइप कर देता है। यहां ग़नीमत यह है कि हमारी regex पहले ही उसे पकड़ लेती है, इसलिए StampMitra तक कॉल जाती ही नहीं। अगर कॉल चली भी जाती, तो डॉक्स के अनुसार जवाब 400 और संदेश "pan must match the format ABCDE1234F" के साथ आता:

JSON
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "pan must match the format ABCDE1234F",
    "requestId": "a1b2c3d4-e5f6-4a1b-8c2d-9f0e1a2b3c4d"
  }
}

तीसरा पार्टनर "रश्मि" है। उसका PAN format में ठीक है, पर API का जवाब valid: false है। यह 200 पर आया एक पूरा जवाब है, कोई error नहीं। सुनील की स्क्रीन पर लिखा आता है कि "यह PAN मान्य नहीं मिला, कृपया दोबारा जांचकर भरें", और सिस्टम इस पर दोबारा कॉल नहीं करता। रश्मि को फ़ोन करके पूछने पर पता चलता है कि उसने पुराना कार्ड पढ़कर नंबर भरा था। सही नंबर के साथ दूसरी कोशिश सफल होती है, और अब इसका नया referenceId बनता है। ध्यान दीजिए कि दोनों कोशिशों की अपनी-अपनी referenceId है। डॉक्स के मुताबिक़ नकारात्मक नतीजा भी पूरा, billable verification है, इसलिए रश्मि के मामले में दोनों कॉल गिनी जाएंगी।

इस दिन के अंत में सुनील के पास तीन रिकॉर्ड हैं और हर एक के साथ एक referenceId जुड़ा है। अगर अगले महीने कोई पूछे कि रश्मि का PAN पहली बार में क्यों अस्वीकार हुआ, तो जवाब तैयार है, क्योंकि पहली जांच का रिकॉर्ड भी सुरक्षित है। यही अनुशासन छोटे-से कोड को एक भरोसेमंद प्रक्रिया बना देता है।

Sandbox में टेस्ट कैसे करें, बिना असली कॉल किए भी

StampMitra के pricing पेज के अनुसार Sandbox में test-mode कॉल sandbox डेटा पर चलती हैं और आपके wallet से पैसे नहीं काटतीं। फिर भी अपने यूनिट टेस्ट में असली नेटवर्क कॉल न रखें, क्योंकि वे धीमे होते हैं और नेटवर्क पर निर्भर होते हैं। नीचे दोनों भाषाओं में mock वाले टेस्ट हैं, और उसके बाद sandbox key से एक छोटा "स्मोक टेस्ट"।

Python: pytest और mock

cURL
pip install pytest
Python
# test_pan.py
from unittest.mock import patch, MagicMock
import requests
from stampmitra_pan import verify_pan


def fake_response(status, body):
    r = MagicMock()
    r.status_code = status
    r.json.return_value = body
    if status >= 400:
        r.raise_for_status.side_effect = requests.HTTPError(response=r)
    return r


@patch.dict("os.environ", {"STAMPMITRA_API_KEY": "sk_test_dummy"})
@patch("stampmitra_pan.requests.post")
def test_valid_pan(post):
    post.return_value = fake_response(200, {
        "referenceId": "SMV-20260927-8F42K7", "valid": True, "panStatus": "VALID"
    })
    out = verify_pan("ABCDE1234F", "Rahul Kumar")
    assert out["valid"] is True
    _, kwargs = post.call_args
    assert kwargs["json"] == {"pan": "ABCDE1234F", "name": "Rahul Kumar"}
    assert kwargs["headers"]["Authorization"] == "Bearer sk_test_dummy"


@patch.dict("os.environ", {"STAMPMITRA_API_KEY": "sk_test_dummy"})
@patch("stampmitra_pan.requests.post")
def test_bad_format_raises(post):
    post.return_value = fake_response(400, {
        "error": {"code": "BAD_REQUEST",
                  "message": "pan must match the format ABCDE1234F",
                  "requestId": "a1b2c3d4-e5f6-4a1b-8c2d-9f0e1a2b3c4d"}
    })
    try:
        verify_pan("BADPAN")
        assert False, "should raise"
    except requests.HTTPError as e:
        assert e.response.status_code == 400

Node.js: node:test और fetch का mock

JavaScript
// pan.test.js  (run: node --test)
const test = require("node:test");
const assert = require("node:assert");

process.env.STAMPMITRA_API_KEY = "sk_test_dummy";
const { verifyPan, StampMitraError } = require("./stampmitra-pan");

test("valid PAN returns referenceId", async () => {
  global.fetch = async (url, opts) => {
    assert.strictEqual(url, "https://api-b2b.stampmitra.in/v1/verify/pan");
    assert.strictEqual(opts.headers.Authorization, "Bearer sk_test_dummy");
    assert.deepStrictEqual(JSON.parse(opts.body), { pan: "ABCDE1234F" });
    return {
      ok: true,
      status: 200,
      json: async () => ({ referenceId: "SMV-20260927-8F42K7", valid: true }),
    };
  };
  const out = await verifyPan("ABCDE1234F");
  assert.strictEqual(out.valid, true);
});

test("401 becomes StampMitraError", async () => {
  global.fetch = async () => ({
    ok: false,
    status: 401,
    json: async () => ({
      error: { code: "UNAUTHORIZED", message: "Missing or invalid API key.", requestId: "r1" },
    }),
  });
  await assert.rejects(() => verifyPan("ABCDE1234F"), (e) => {
    assert.ok(e instanceof StampMitraError);
    assert.strictEqual(e.status, 401);
    return true;
  });
});

असली sandbox स्मोक टेस्ट

Mock टेस्ट के बाद एक बार असली sk_test_ key से cURL या अपनी स्क्रिप्ट चलाइए और देखिए कि header, scope और body सही हैं। इससे वे गलतियां पकड़ में आती हैं जिन्हें mock नहीं पकड़ सकते, जैसे गलत scope या typo वाला URL। Sandbox और production key के अंतर पर sandbox बनाम production API keys गाइड में अलग से बात हुई है। और अगर आप AI टूल (Claude, Cursor आदि) से कोड बनवा रहे हैं, तो Build with AI और MCP सर्वर से वह सीधे sandbox पर टेस्ट कर सकता है।

Production में जाने से पहले की checklist

Sandbox में सब ठीक चलने के बाद production की तरफ़ बढ़ने से पहले इन बातों को एक-एक करके जांच लें। इनमें से कई चीज़ें कोड से बाहर की हैं।

  • Production key: यह sk_live_ से शुरू होती है और auth docs के अनुसार तभी मिलती है जब आपका organization KYC से approved हो। इसके लिए developer onboarding verification policy देख लें।
  • Wallet balance: priced services में सफल कॉल पर wallet से पैसे कटते हैं (pricing पेज: "each successful call is deducted from your API credit wallet")। कम balance पर 402 आएगा, इसलिए balance की निगरानी और अलर्ट लगाएं। मौजूदा दर pricing पेज पर देखें।
  • Key अलग रखें: sandbox और production की keys अलग environment variables में रहें, और कोड एक ही रहे। सिर्फ़ मान बदले।
  • Rate limit: डॉक्स के नमूने में 60 requests प्रति 60 seconds का उदाहरण है। अपनी असली सीमा की पुष्टि अपने खाते में देखें, और बैच में काम करना हो तो कतार लगाकर एक-एक करके भेजें।
  • निगरानी: हर कॉल पर status, error.code, requestId और referenceId लॉग करें (PAN नहीं, या मास्क करके)। 402, 401, 403 पर अलर्ट सीधे आपकी टीम को जाए।
  • Fallback: अगर 502 आए, तो यूज़र को अटकाइए नहीं। उसका आवेदन "जांच बाक़ी है" की स्थिति में रखिए और बाद में दोबारा चलाइए।
  • Timeout: 10 से 15 सेकंड का timeout एक व्यावहारिक शुरुआती मान है। यह मेरा सुझाव है, StampMitra का कोई आधिकारिक आंकड़ा नहीं।

आख़िरी जांच: एक बार पूरी प्रक्रिया ज़ोर से बोलकर देखिए

Production खोलने से ठीक पहले अपनी टीम के साथ पूरी कहानी एक बार शब्दों में दोहराइए: यूज़र PAN भरता है, हमारा सर्वर उसे साफ़ करता है और format जांचता है, फिर StampMitra को कॉल जाती है, जवाब से हम valid, panStatus और नाम का नतीजा पढ़ते हैं, referenceId के साथ रिकॉर्ड सेव होता है, और यूज़र को सीधी भाषा में नतीजा दिखता है। अगर इस कहानी में कोई कड़ी बोलते हुए अटकती है, तो समझिए कि वहीं कोई गड़बड़ छिपी है। कई बार टीम को यहीं पता चलता है कि कोई अलर्ट लगाना भूल गया था या कोई error संदेश तय ही नहीं हुआ था। यह दस मिनट की बातचीत बाद की रातों की परेशानी बचा सकती है।

एक छोटा "रोलआउट" तरीक़ा

सीधे सभी यूज़र्स पर न खोलें। पहले एक दिन सिर्फ़ ऑपरेशंस टीम के भीतर चलाइए, फिर नए पार्टनर्स के 10 प्रतिशत पर, और हर चरण में देखिए कि valid: false का अनुपात, 429 की संख्या और औसत जवाब का समय सामान्य है या नहीं। यह प्रतिशत एक उदाहरण है, कोई नियम नहीं।

लागत का हिसाब कैसे रखें

Production में हर सफल कॉल की क़ीमत ₹5 है, जो आपके API credit wallet से कटती है। यह रक़म छोटी लगती है, पर अगर कोई बग हर पेज लोड पर कॉल चलवा दे तो महीने के अंत में बिल चौंका सकता है। इसलिए दो आदतें डालिए। पहली, हर कॉल पर एक गिनती (counter) बढ़ाइए, और रोज़ की गिनती का एक अलर्ट लगाइए, ताकि असामान्य उछाल दिखते ही कोई देख ले। दूसरी, नए कोड को पहले sandbox में चलाइए, जहां कॉल wallet से नहीं कटतीं। कितने PAN रोज़ जांचने हैं, इसका अंदाज़ा आप अपने onboarding की संख्या से लगा सकते हैं: जितने नए पार्टनर, उतनी कॉल, और ग़लत टाइपिंग की वजह से कुछ अतिरिक्त। अपनी असली दर हमेशा pricing पेज पर जांचिए, क्योंकि दरें बदल सकती हैं और इस लेख की तारीख़ 2026-10-16 है।

आम गलतियां और troubleshooting

अनुभव से देखा गया है कि ज़्यादातर समस्याएं नीचे की सूची में से किसी एक में आती हैं। लक्षण से कारण तक जाने के लिए इस तालिका का इस्तेमाल कीजिए:

लक्षणसंभावित कारणउपाय
401 UNAUTHORIZEDKey खाली है, गलत है, या header में "Bearer " के बाद जगह नहीं.env लोड हुई या नहीं देखें; header Authorization: Bearer <key> हो, या x-api-key इस्तेमाल करें
403 FORBIDDENKey पर verification.pan.read scope नहींसही scope वाली key बनाएं
400 BAD_REQUESTPAN ABCDE1234F format में नहीं, या अतिरिक्त खाली जगहstrip()/trim() और बड़े अक्षर करें
402 PAYMENT_REQUIREDWallet में balance कमBalance भरें, फिर दोबारा कोशिश
429 TOO_MANY_REQUESTSएक साथ बहुत कॉलकतार, backoff और cache लगाएं
502 INTERNAL_ERRORUpstream सेवा तक नहीं पहुंचेसीमित retry; बार-बार हो तो requestId के साथ support को लिखें
Node में कोई exception नहीं पर नतीजा खालीres.ok जांचा नहीं गया!res.ok पर error बनाएं
Python में कॉल अनंत काल तक अटकीtimeout नहीं दियाहर requests कॉल में timeout= दें
nameMatchScore की तुलना गलतयह string हैint() या Number() से बदलें
valid: false पर retry का तूफ़ानइसे error मान लियाइसे अंतिम नतीजा मानें

जब कुछ समझ न आए

सबसे पहले वही कॉल cURL से चलाकर देखिए। अगर cURL में चल जाती है तो समस्या आपके कोड में है, और अगर वहां भी fail है तो key, scope या वॉलेट की जांच करें। फिर भी हल न मिले, तो जवाब का requestId और समय लेकर [email protected] पर लिखिए। अपनी key या असली PAN कभी mail में न भेजें। सपोर्ट पेज पर भी रास्ते हैं।

StampMitra आपकी कैसे मदद करता है

StampMitra एक निजी LegalTech प्लेटफ़ॉर्म है, जिसे Bani Global Industries LLP (LLPIN ACI-6373) चलाती है। यह सरकारी संस्था या law firm नहीं है। Developer प्लेटफ़ॉर्म पर आज जो उपलब्ध है, वह इस प्रकार है: PAN के लिए POST /v1/verify/pan endpoint, जो असली समय में जवाब देता है और हर कॉल पर referenceId लौटाता है; API key से authentication (Authorization: Bearer या x-api-key), sandbox और production के लिए अलग sk_test_/sk_live_ keys, scopes और संवेदनशील scopes पर IP allowlist; एक जैसी { error: { code, message, requestId } } बनावट वाले errors; और हर endpoint पर server-side rate limit।

PAN के अलावा उसी तरह की verification के लिए GSTIN, Vehicle RC, Voter ID, Driving Licence, Passport, CIN, Udyam, PAN से GSTIN, PAN से Udyam और bank account जैसे endpoints हैं, और e-stamp के आदेश के लिए अलग API है। इन सबके दस्तावेज़ products और verification docs में हैं। अगर आपको PAN के बाद पूरी कारोबारी जांच बनानी है, तो फ़िनटेक और NBFC के लिए KYC API वाला use case देखिए।

कुछ सीमाएं भी साफ़ हैं। StampMitra आपको जांच का नतीजा देता है, पर उस नतीजे पर क्या फ़ैसला लेना है, यह आपके व्यवसाय और नियमों पर निर्भर है। नाम मिलान की सीमा आपको खुद तय करनी होगी, और डेटा सुरक्षा से जुड़े दायित्वों के लिए अपने सलाहकार से बात करनी होगी। Production key के लिए आपके organization का KYC approval ज़रूरी है।

आगे की योजनाएं (ये वादे नहीं, इरादे हैं): डेवलपर्स के लिए और भाषाओं के उदाहरण और SDKs तथा Postman collection का दायरा बढ़ाना, और हिंदी में और गाइड जोड़ना। मौजूदा स्थिति हमेशा SDKs और Postman पेजों पर देखें। शुरू करने के लिए मुफ़्त sandbox खाता बनाइए, या अपने सवाल contact पेज से भेजिए।

API reference एक नज़र में

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).

अक्सर पूछे जाने वाले सवाल

Node.js से PAN वेरिफाई करने के लिए कौन सा endpoint इस्तेमाल होता है?

StampMitra में POST https://api-b2b.stampmitra.in/v1/verify/pan endpoint है। Header में Authorization: Bearer <key> भेजें और JSON body में pan दें। Node 18 या उससे नए संस्करण में बिना अलग library के fetch से यह कॉल चल जाती है।

Python में PAN verification API कॉल के लिए क्या चाहिए?

requests जैसी HTTP library, एक sk_test_ sandbox key और environment variable STAMPMITRA_API_KEY काफ़ी हैं। requests.post से /v1/verify/pan पर json={"pan": "ABCDE1234F"} भेजें, timeout ज़रूर दें और जवाब से valid तथा referenceId पढ़ें।

PAN API के लिए API key पर कौन सा scope चाहिए?

PAN verification के लिए key पर verification.pan.read scope होना ज़रूरी है। अगर key वैध है पर यह scope नहीं है, तो API 403 FORBIDDEN लौटाती है। ऐसे में सही scope वाली key इस्तेमाल करें, कोड में बदलाव की ज़रूरत नहीं होती।

valid: false आने का मतलब क्या है, क्या यह error है?

नहीं, यह error नहीं है। StampMitra के डॉक्स के अनुसार valid: false वाला नतीजा भी एक पूरा हुआ और billable verification है, जिसका HTTP status 200 रहता है। इसे यूज़र को PAN मान्य नहीं मिला कहकर दिखाएं और उस पर अपने आप retry न करें।

गलत format वाला PAN भेजने पर क्या होता है?

PAN का format ABCDE1234F से मेल न खाए तो API पहले ही 400 BAD_REQUEST लौटाती है, जिसका संदेश pan must match the format ABCDE1234F जैसा होता है। बेहतर है कि अपने सर्वर पर regex से यही जांच पहले कर लें, ताकि बेवजह कॉल न जाए।

किन errors पर retry करना चाहिए और किन पर नहीं?

429 TOO_MANY_REQUESTS, 502 INTERNAL_ERROR और नेटवर्क timeout पर सीमित बार, बढ़ते अंतराल के साथ retry करें। 400, 401, 402 और 403 पर retry का फ़ायदा नहीं, क्योंकि वहां input, key, scope या wallet balance ठीक करना पड़ता है।

क्या referenceId को idempotency key की तरह इस्तेमाल कर सकते हैं?

नहीं। डॉक्स के अनुसार referenceId (SMV-YYYYMMDD-XXXXXXX) StampMitra का अपना transaction id है, idempotency key नहीं। इसे audit के लिए अपने रिकॉर्ड में सेव करें, और दोहराई गई कॉल रोकने के लिए अपनी तरफ़ cache या ताला रखें।

क्या API key को frontend या मोबाइल ऐप में रख सकते हैं?

नहीं। Key सिर्फ़ आपके backend के environment variable या secrets manager में रहनी चाहिए। Browser या ऐप से सीधे कॉल करने पर key लीक हो सकती है, इसलिए ऐप आपके अपने route को कॉल करे और वह route StampMitra को।

Sandbox में PAN टेस्ट करने पर पैसे कटते हैं क्या?

StampMitra के pricing पेज के अनुसार Sandbox की test-mode कॉल sandbox डेटा पर चलती हैं और आपके wallet से नहीं कटतीं। Production में हर सफल कॉल पर wallet से कटौती होती है, इसलिए पहले sandbox में पूरी जांच कर लें।

नाम का मिलान कैसे करें और स्कोर की सीमा क्या रखें?

Body में वैकल्पिक name भेजने पर जवाब में nameMatchResult और nameMatchScore मिलते हैं। nameMatchScore एक string है। डॉक्स में कोई मान्य सीमा तय नहीं बताई गई, इसलिए अपने डेटा पर परखकर अपनी सीमा चुनें और कमज़ोर मिलान को मैन्युअल समीक्षा में भेजें।

स्रोत

सभी स्रोत 2026-10-16 को देखे गए।

  • StampMitra Developers, PAN verification API (products): endpoint, scope, request और response का आकार, error उदाहरण।
  • StampMitra Developers, API key authentication: Authorization: Bearer और x-api-key, sk_test_ बनाम sk_live_, scopes, IP allowlist, production के लिए KYC approval।
  • StampMitra Developers, Quickstart: sandbox साइन-अप और key सेव करने के चरण।
  • StampMitra Developers, Pricing: sandbox में wallet पर कोई असर नहीं, production में प्रति सफल कॉल कटौती।
  • StampMitra Developers, Build with AI: server-side-only कॉल और STAMPMITRA_API_KEY का सुझाव।
  • StampMitra Developers, API सुरक्षा नीति और डेवलपर प्राइवेसी नीति।
  • Digital Personal Data Protection Act, 2023 (भारत सरकार): इस लेख में सिर्फ़ सामान्य संदर्भ के तौर पर नाम लिया गया है; धाराओं का हवाला नहीं दिया गया।