# Pramana.Anantasutra: India GSTIN, PAN, IFSC Validator (`motivating_bracelet/pramana-anantasutra`) Actor

Bulk-validate and decode Indian IDs with auto-detection: GSTIN checksum, PAN, TAN, CIN, LLPIN, IFSC branch lookup, PIN code post offices, UPI ID and vehicle numbers. Offline, fast, cheap.

- **URL**: https://apify.com/motivating_bracelet/pramana-anantasutra.md
- **Developed by:** [Bhavy Duneja](https://apify.com/motivating_bracelet) (community)
- **Categories:** Developer tools, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 identifier validateds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Pramana.Anantasutra: India ID Validator (GSTIN, PAN, IFSC, PIN, UPI)

Pramana.Anantasutra checks and decodes Indian identifiers in bulk. Paste a column of mixed IDs. It works out what
each value is (GSTIN, PAN, TAN, CIN, LLPIN, IFSC, PIN code, UPI ID or vehicle registration number) and tells you
whether the value is well formed. Where the data allows, it also tells you what the value means: the state, the PAN
holder type, the bank branch, or the post offices of a PIN.

Everything runs offline inside the Actor against reference data built into the image. Nothing you send is passed on
to a government portal or a third-party API. It is part of the **Anantasutra** family of small, cheap, honest tools
for Indian data.

### Who it is for

- **Finance and ops teams** cleaning vendor masters, GST invoices and KYC exports before upload or reconciliation.
- **Developers and AI agents** that need a fast, deterministic "is this GSTIN / IFSC / PIN real-looking?" check.
- **Marketplaces and lenders** screening onboarding forms for typos before a paid verification API call.

### What is checked

| Type | Example | Checked offline | Decoded |
|---|---|---|---|
| GSTIN | `27AAACR5055K1Z7` | length, state code (incl. 38 Ladakh, 97, 99, legacy 25/28), embedded PAN or TAN, entity number, **check character (GSTN checksum)**. OIDAR (`99..OS.`), UIN (`..UN.`) and non-resident (`..NR.`) registrations are recognised. | state, PAN, holder type, registration number |
| PAN | `AAACR5055K` | structure, holder-type letter, sequence | holder type (company, individual, HUF, ...) |
| TAN | `DELM12345L` | structure; placeholder jurisdictions such as `XXX` are rejected | jurisdiction code, deductor initial |
| CIN | `L17110MH1973PLC019786` | listing flag, NIC code, ROC state, year, company category (one of the 12 MCA codes such as PLC, PTC, OPC, GOI, NPL) | all six parts |
| LLPIN | `AAB-1234` | structure | series and number |
| IFSC | `SBIN0000300` | structure, reserved `0`, bank code, **branch lookup** in the RBI list | bank, branch, address, city, district, state, MICR, NEFT/RTGS/IMPS/UPI flags |
| PIN code | `560001` | format, **lookup in the India Post directory** | district, state, zone, post offices |
| UPI ID | `name@okaxis` | format, handle against NPCI and SEBI app lists | app and PSP bank |
| Vehicle | `MH12AB1234`, `22BH1234AA` | state code (incl. legacy TS/OR/UA), RTO number not 0, number 1-9999, BH-series year, temporary marks | state, RTO code, series |

Near misses get a specific message instead of "unrecognised". Examples: a GSTIN one character short, an IFSC with a
missing digit (`SBIN000300`), a PIN with the letter O typed for zero (`5600O1`, "did you mean 560001?") or a
registration number with five digits.

### Input

```json
{
  "ids": ["27AAACR5055K1Z7", "SBIN0000300", "560001", "shop.name@okaxis", "MH12AB1234", "N/A"],
  "type": "auto",
  "includePostOffices": true,
  "maxPostOffices": 50
}
```

- `ids`: one value per line, any mix of types, up to 100,000 per run. A single string with line breaks also works.
- `type`: leave on `auto`, or force one type for the whole list. To force a type for one value, prefix it:
  `pin:110001`, `pan:AAACX1234F`.
- Spreadsheet leftovers are cleaned before checking: surrounding spaces, a leading apostrophe (`'27AAACR5055K1Z7`),
  CSV quotes, zero-width spaces, `="..."` text formulas and the `.0` that pandas adds (`560001.0`).

### Output

One row per input value, in input order:

```json
{
  "index": 1,
  "input": "SBIN0000300",
  "detectedType": "ifsc",
  "valid": true,
  "normalized": "SBIN0000300",
  "decoded": { "bank": "State Bank of India", "branch": "MUMBAI MAIN", "city": "Greater Bombay", "district": "Mumbai", "state": "Maharashtra", "micr": "400002010", "neft": true, "rtgs": true, "imps": true, "upi": true },
  "errors": [],
  "warnings": []
}
```

`errors` and `warnings` are `"CODE: message"` strings, so you can filter on the code. `valid` is `true` only when
there are no errors. A run summary (counts by type, how many rows were charged, data versions) is saved as the
`SUMMARY` record in the key-value store.

Common codes: `CHECKSUM_MISMATCH`, `LENGTH`, `NOT_FOUND`, `UNKNOWN_BANK_CODE`, `UNVERIFIABLE` (Army Postal Service
PIN not in the public directory), `SPACES` (UPI ID with a space; it is never "fixed" for you), `PLACEHOLDER`,
`EXCEL_SCIENTIFIC_NOTATION`, `TOO_LONG`, `AADHAAR_NOT_SUPPORTED`, `UNRECOGNIZED`.

### Pricing

Pay per result. You only pay for values that were actually validated.

| Event | Price | When |
|---|---|---|
| Identifier validated | $0.0004 ($0.40 per 1,000) | one result row for a value that was checked |
| Actor start | $0.00005 | charged by Apify once per run at the default 512 MB memory |

Examples: 1,000 IDs cost about $0.40, and 100,000 IDs about $40. **Never charged:** Aadhaar numbers
(`AADHAAR_NOT_SUPPORTED`), blank cells (`EMPTY`), placeholders (`PLACEHOLDER`: `N/A`, `null`, `None`, `NaN`, `-`,
`#N/A`, `0`, `999999` and similar), values over 256 characters (`TOO_LONG`), and Excel scientific-notation numbers
such as `5.6E+05` (`EXCEL_SCIENTIFIC_NOTATION`). These still get a row so your output lines up with your input. The
run stops cleanly at your maximum charge limit and never bills beyond it.

### Privacy: Aadhaar numbers are refused

This Actor does not process Aadhaar numbers (Aadhaar Act, 2016). Any value that **contains** a 12-digit number
starting with 2-9, or a 16-digit number (Virtual ID), is refused and masked to the last four digits, wherever the
digits appear and however they are grouped (spaces, dots, dashes, commas or other punctuation between the digits):
`Aadhaar No. 2345 6789 0127`, `uid 234567890127`, `'234567890127`, `234567890127.0`, `234,567,890,127` (Excel's
thousands separator), `2345–6789–0127` or `234567890127@upi`. That includes UPI IDs built on 12-digit numbers such
as `919876543210@paytm`. Refused rows are free, and the full number is never written to the dataset, the summary or
the log. Privacy comes first, so a cell holding several numbers that together make 12 digits (two PINs such as
`560001, 560034`) is refused the same way: put one identifier per line.

### Limits and what is not verified

- 100,000 values per run, 256 characters per value. Larger lists are rejected up front with a "split the list" message,
  and nothing is charged.
- **Structure and checksums, not live status.** A valid GSTIN may be cancelled, and a valid PAN or TAN may not be
  allotted. The PAN and TAN check letters use an unpublished algorithm and are not verified. TAN jurisdiction codes and
  RTO office numbers are not checked against an official list (`jurisdictionVerified: false`, `rtoVerified: false`).
  UPI IDs are not checked for existence. CIN status (active or struck off) is not checked.
- IFSC branches come from the razorpay/ifsc release bundled at build time (for example v2.0.62 with 183,214 IFSCs).
  Branches opened later show as `NOT_FOUND`, and IFSCs of merged banks are reported as not found.
- PIN codes come from the India Post directory snapshot (as of 2025-10-03: 19,586 PINs, 165,593 post offices). Army
  Postal Service PINs (9xxxxx) are not published, so apart from the two base post offices listed they are reported
  as `UNVERIFIABLE`, `valid: false`.
- A CIN whose company-category code is not one MCA issues (for example `XYZ`) is invalid (`UNKNOWN_COMPANY_TYPE`).
  The NIC industry code inside a CIN is only checked for shape (5 digits), not against the NIC tables.

### Use it from Claude, ChatGPT or Cursor (Apify MCP server)

Pramana works well as a tool for AI agents: the output is deterministic and every problem has a machine-readable code.

1. In Claude (Settings > Connectors), Claude Code, Cursor or ChatGPT (developer-mode connectors), add the Apify MCP
   server `https://mcp.apify.com` and sign in with your Apify account. To expose only this Actor, use
   `https://mcp.apify.com?tools=motivating_bracelet/pramana-anantasutra`. To run the server locally instead, use
   `npx -y @apify/actors-mcp-server --actors motivating_bracelet/pramana-anantasutra` with your Apify API token in the
   `APIFY_TOKEN` environment variable.
2. Ask in plain language, for example: *"Validate these GSTINs and IFSCs with motivating_bracelet/pramana-anantasutra and
   list the invalid ones with the reason."*

The current setup steps for each client are in Apify's MCP documentation (docs.apify.com, "MCP server").

From code, one synchronous call returns the rows:

```bash
curl -s -X POST "https://api.apify.com/v2/acts/motivating_bracelet~pramana-anantasutra/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["27AAACR5055K1Z7", "560001"]}'
```

### FAQ

**Is a valid GSTIN an active registration?** No. "Valid" means the format, state code, embedded PAN and checksum are
right, which catches nearly every typo. Registration status is only available from the GST portal.

**Why is my UPI ID with a space marked invalid?** UPI IDs never contain spaces, and deleting them can produce a
different person's ID. Pramana reports `SPACES` and leaves the value unchanged.

**Why is `999999` not a valid PIN?** It is the usual filler for an unknown PIN. It is treated as a placeholder and
not charged. Other unlisted 9xxxxx PINs cannot be confirmed and are marked `UNVERIFIABLE`.

**My sheet shows 5.6E+05 instead of a PIN.** Excel has already dropped digits. Format the column as Text, export it
again and re-run. These rows are not charged.

**Can I send Aadhaar numbers to have them validated?** No. They are refused and masked by design.

**Is this an official government service?** No. It is an independent tool built on public data. It is not affiliated
with GSTN, the Income Tax Department, MCA, RBI, NPCI, India Post or UIDAI.

### Data sources and licences

- **India Post PIN directory**: "All India Pincode Directory" by the Department of Posts, Ministry of Communications,
  Government of India, on data.gov.in, under the Government Open Data License - India (GODL-India). *Contains data
  from the "All India Pincode Directory" published by the Department of Posts, Ministry of Communications, Government
  of India, on the Open Government Data Platform India (data.gov.in), used under the Government Open Data License -
  India (GODL-India).* The bundled snapshot was compacted from the khush/india-pincodes v1.0.0 redistribution of that
  dataset. data.gov.in lists the dataset as updated 2025-10-03.
- **IFSC and bank list**: razorpay/ifsc (github.com/razorpay/ifsc). The dataset is in the public domain and the code
  is MIT licensed. It is compiled from RBI NEFT/RTGS lists and NPCI member lists.
- **UPI handles**: compiled from NPCI's "UPI 3rd Party Apps" and "PPI Apps" tables and SEBI's list of UPI apps for
  public issues (checked 2026-10-20). An unknown handle is a warning, never an error.
- **State, GST, vehicle and ROC codes**: ISO 3166-2:IN (with the 2023 update), CBIC GST state codes, MoRTH
  registration codes and MCA CIN codes, verified 2026-10-20.

# Actor input Schema

## `ids` (type: `array`):

One identifier per line, any mix of types (max 100,000 per run). Spaces, dots and hyphens are ignored, and spreadsheet leftovers (a leading apostrophe, quotes, zero-width spaces, a trailing .0) are removed. Blank and placeholder cells (N/A, null, -) are free. To force a type for a single value, prefix it, e.g. "pin:110001" or "pan:AAACX1234F".

## `type` (type: `string`):

Leave on 'Detect automatically' for mixed lists. Pick a type to validate every value as that type (useful when a list contains only PIN codes or only PANs).

## `includePostOffices` (type: `boolean`):

Add the post offices (name, type, delivery status) that serve each PIN code.

## `maxPostOffices` (type: `integer`):

Caps the post office list per PIN code (the total count is always reported).

## Actor input object example

```json
{
  "ids": [
    "27AAACX1234F1ZN",
    "27ABCDE1234F1Z5",
    "AAACX1234F",
    "DELM12345L",
    "U72900KA2015PTC123456",
    "AAB-1234",
    "SBIN0000300",
    "HDFC0CAACOB",
    "560001",
    "110001",
    "shop.name@okaxis",
    "MH12AB1234",
    "22BH1234AA"
  ],
  "type": "auto",
  "includePostOffices": true,
  "maxPostOffices": 50
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset items, one per input identifier.

## `decoded` (type: `string`):

The same dataset showing everything decoded from each identifier.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "ids": [
        "27AAACX1234F1ZN",
        "27ABCDE1234F1Z5",
        "AAACX1234F",
        "DELM12345L",
        "U72900KA2015PTC123456",
        "AAB-1234",
        "SBIN0000300",
        "HDFC0CAACOB",
        "560001",
        "110001",
        "shop.name@okaxis",
        "MH12AB1234",
        "22BH1234AA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("motivating_bracelet/pramana-anantasutra").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "ids": [
        "27AAACX1234F1ZN",
        "27ABCDE1234F1Z5",
        "AAACX1234F",
        "DELM12345L",
        "U72900KA2015PTC123456",
        "AAB-1234",
        "SBIN0000300",
        "HDFC0CAACOB",
        "560001",
        "110001",
        "shop.name@okaxis",
        "MH12AB1234",
        "22BH1234AA",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("motivating_bracelet/pramana-anantasutra").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "ids": [
    "27AAACX1234F1ZN",
    "27ABCDE1234F1Z5",
    "AAACX1234F",
    "DELM12345L",
    "U72900KA2015PTC123456",
    "AAB-1234",
    "SBIN0000300",
    "HDFC0CAACOB",
    "560001",
    "110001",
    "shop.name@okaxis",
    "MH12AB1234",
    "22BH1234AA"
  ]
}' |
apify call motivating_bracelet/pramana-anantasutra --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,motivating_bracelet/pramana-anantasutra"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/G028pNMkRdjNpYavd/builds/wjpECqf0UrknQPLE0/openapi.json
