# Sthana.Anantasutra: India Address Normalizer & PIN Validator (`motivating_bracelet/sthana-anantasutra`) Actor

Parse messy Indian addresses into house, street, locality, city, district, state and PIN. Validates the PIN against state/district, fixes city aliases, scores confidence. Offline.

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

## Pricing

from $0.80 / 1,000 address normalizeds

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

## Sthana.Anantasutra: India Address Normalizer and PIN Code Validator

Sthana.Anantasutra turns messy Indian addresses into clean, structured ones. Paste addresses the way customers type
them, in English, Hindi or Hinglish, with or without a PIN code. Each one is split into house, building, street,
landmark, locality, tehsil, city, district, state and PIN. The PIN is checked against the India Post directory, and
you get back a clean one-line address, a confidence score and a list of problems, each with a machine-readable code.

Everything runs offline inside the Actor against the India Post PIN directory built into the image. No LLM, no
geocoding API, and nothing you send is passed to a third party. It is part of the **Anantasutra** family of small,
cheap, honest tools for Indian data (Pramana.Anantasutra validates GSTIN, PAN, IFSC, PIN and UPI IDs).

### Who it is for

- **E-commerce and D2C brands** cleaning checkout addresses from Shopify, WooCommerce, Amazon or Magento before
  printing courier labels, to cut misroutes and RTO (return to origin) costs.
- **Logistics, 3PL and courier operations** that need to catch a wrong PIN, a state that contradicts the PIN, or an
  address with no house number before dispatch.
- **CRM and data teams** standardising old customer address books (Bombay, Madras, Gurgaon, Orissa, undivided Andhra
  Pradesh) so records can be deduplicated and segmented by city, district and state.
- **Developers and AI agents** that need a fast, deterministic address parser with clear error codes.

### What it does

- **Splits the address** into `careOf`, `house`, `building`, `street`, `landmark`, `locality`, `subDistrict`
  (tehsil / taluka), `city`, `district`, `state`, `stateCode`, `pincode` and `country`.
- **Checks the PIN** against the India Post directory: does it exist, does it belong to the state, city and district
  written in the address, and does the locality's own post office have a different PIN (`LOCALITY_PIN_MISMATCH`,
  the most common real delivery error)?
- **Reads PINs in every common form**: `400021`, `400 021`, `400-021`, `Mumbai-400021`, `(400021)`, `PIN:400021`,
  Devanagari digits (`४०००२१`) and pandas' `400021.0`. A malformed PIN (`4OOO21`, `40002`, `000000`) is reported as
  `PIN_INVALID_FORMAT` with a suggestion, and the city is still recognised. Postal-zone shorthand such as
  `Laxmi Nagar Delhi 92` or `Bangalore 38` gets the full PIN as a suggestion (110092, 560038).
- **Modernises names**, with an issue that says what changed: old city names (Bombay to Mumbai, Madras to Chennai,
  Calcutta to Kolkata, Gurgaon to Gurugram, Allahabad to Prayagraj, Poona, Baroda, Simla, Trivandrum, Cochin, Vizag
  and more), state spellings and abbreviations (`KA`, `UP`, `raj`, `Gujrat`, `Orissa`, `Uttaranchal`, Hindi state
  names and abbreviations such as `उ.प्र.` or `म.प्र.`) and states that were split: a pre-2014 `Hyderabad, Andhra Pradesh` becomes Telangana and a pre-2019 `Leh,
  Jammu and Kashmir` becomes Ladakh (`STATE_MODERNIZED` warning, the address stays valid).
- **Never swaps a town for a different city.** Only true aliases are renamed. A town or district written in the
  address (Kanchipuram, Dharwad, Palwal, Raigad, Chittoor) is kept as written, not replaced by the nearest big city.
- **Understands Indian address habits**: `C/O` and `S/O` names (kept in `careOf`), landmarks (`near`, `opp`, `bhd`,
  `ke paas`, `के पास`), village / post office / tehsil / district markers (`Vill.`, `PO`, `At Post`, `Tal.`, `Teh.`,
  `Dist.`, `PS` for police station), Hinglish house markers (`Ghar no`, `Makan`, `H no`) and Hindi ones (`मकान नं`,
  `फ्लैट`). Hindi and other non-Latin words are kept as written; Hindi city and state names are recognised.
- **Keeps phone numbers out of the result.** A mobile number typed into the address (`Mob: 9876543210`) is left out of
  every output field and reported as `PHONE_REMOVED`.
- **Suggests PIN codes** when the PIN is missing, malformed, not in the directory or does not match the locality.
- **Scores confidence** from 0 to 1 so you can route low-confidence addresses to a human or a customer callback.

### Input

```json
{
  "addresses": [
    "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
    "#45, 2nd Cross, 4th Block, Koramangala, Bangalore 560034, KA",
    "मकान नं 12, गांधी नगर, जयपुर, राजस्थान 302001",
    "N/A"
  ],
  "structuredAddresses": [
    { "address1": "Flat 4, Sunshine Residency, 27th Main", "address2": "HSR Layout", "city": "Bangalore", "province": "Karnataka", "zip": "560102", "country": "India" }
  ],
  "outputCase": "title",
  "includePincodeDetails": false,
  "suggestPincode": true
}
```

- `addresses`: one complete address per line (free text). A single string with line breaks also works.
- `structuredAddresses`: addresses you already hold as fields, for example a checkout export. Field names are matched
  ignoring case, spaces, `_`, `-` and `.`, so `Address Line 1`, `address_1`, `AddressLine1` and `address1` are the
  same field. Accepted: `line1`-`line3`, `address1`-`address3`, `address_1`, `AddressLine1`-`AddressLine3`,
  `address`, `street` (a string or a list of lines), `house`, `building`, `locality`, `area`, `landmark`, `city`,
  `district`, `state` / `province` / `region` / `StateOrRegion` (Magento's nested `region` object too), `pincode` /
  `pin` / `zip` / `postcode` / `PostalCode`, and `country` / `country_code` / `CountryCode`. Names, phone numbers,
  e-mails and IDs are skipped quietly. Any other filled field is listed in an `UNKNOWN_FIELD` warning, never
  silently dropped.
- `outputCase`: `title` (Title Case, recommended), `upper` (UPPER CASE, for courier labels) or `original`.
- `includePincodeDetails`: add the PIN's district(s), state and post-office names to each row.
- `suggestPincode` (default on): suggest PIN codes when the PIN is missing or wrong.

### Output

One row per address, in input order (free-text addresses first, then structured ones):

```json
{
  "index": 0,
  "input": "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
  "formatted": "Flat 302, Sai Krupa Apts, MG Road, Near Hanuman Mandir, Andheri East, Mumbai, Maharashtra 400069, India",
  "confidence": 0.87,
  "valid": true,
  "careOf": null,
  "house": "Flat 302",
  "building": "Sai Krupa Apts",
  "street": "MG Road",
  "landmark": "Near Hanuman Mandir",
  "locality": "Andheri East",
  "subDistrict": null,
  "city": "Mumbai",
  "district": "Mumbai Suburban",
  "state": "Maharashtra",
  "stateCode": "MH",
  "pincode": "400069",
  "country": "India",
  "issues": [{ "code": "CITY_NORMALIZED", "severity": "info", "message": "\"Bombay\" written as \"Mumbai\"." }],
  "filledFromPincode": ["state", "district"]
}
```

- `formatted`: the clean one-line address, ready for a label. Repeats are skipped (a district equal to the city, the
  city of a city-state).
- `valid`: `true` only when there is no error-level issue, the PIN is in the India Post directory, and a city,
  locality or tehsil was found. Warnings do not make a row invalid; filter on them if you need stricter rules.
- `confidence`: 0 to 1, from how many components were found and whether they agree with each other and the PIN.
- `issues`: `{ code, severity, message }` with severity `error`, `warning` or `info`.
- `suggestedPincodes`: up to 5 `{ pincode, office, district, state }` when the PIN is missing or wrong.
- `filledFromPincode`: which fields came from the PIN directory instead of the address text.
- `pincodeDetails` (when `includePincodeDetails` is on) and `unparsed` (text that could not be placed).

A run summary (valid / invalid / blank counts, issue counts, average confidence, PIN directory version) is saved as
the `SUMMARY` record in the key-value store.

#### Issue codes

| Severity | Codes |
|---|---|
| error | `PIN_NOT_FOUND`, `PIN_INVALID_FORMAT`, `PIN_PLACEHOLDER` (`999999`, `111111`), `STATE_PIN_MISMATCH`, `CITY_STATE_MISMATCH`, `CITY_PIN_MISMATCH`, `COUNTRY_NOT_INDIA`, and for rows that were not processed: `EMPTY`, `PLACEHOLDER`, `TOO_LONG` |
| warning | `PIN_MISSING`, `PIN_ARMY_POSTAL`, `MULTIPLE_PINS`, `STATE_MODERNIZED`, `LOCALITY_PIN_MISMATCH`, `CITY_DISTRICT_MISMATCH`, `DISTRICT_PIN_MISMATCH`, `HOUSE_MISSING`, `LIKELY_INCOMPLETE`, `CITY_MISSING`, `UNRECOGNIZED_LOCATION`, `COUNTRY_UNRECOGNIZED`, `UNKNOWN_FIELD`, `FIELD_IGNORED` |
| info | `CITY_NORMALIZED`, `STATE_NORMALIZED`, `STATE_FROM_CITY`, `CITY_NOT_FOUND`, `PHONE_REMOVED`, `NON_LATIN_TEXT`, `UNPARSED_TEXT`, `PIN_DATA_CONFLICT`, `DIRECTORY_UNAVAILABLE` |

`LOCALITY_PIN_MISMATCH` is a warning when the locality's post office is in the same district as the PIN, and only
`info` when it is in another district of the same city, because big cities repeat locality names (Delhi has a
Shastri Nagar in North Delhi, 110052, and a Shastri Nagar post office in East Delhi, 110031).

### Pricing

Pay per result. You only pay for addresses that were actually processed.

| Event | Price | When |
|---|---|---|
| Address normalized | $0.0008 ($0.80 per 1,000) | one result row for an address that was parsed and checked |
| Actor start | $0.00005 | charged by Apify once per run at the default 512 MB memory |

Examples: 1,000 addresses cost about $0.80, and 25,000 addresses (the per-run maximum) about $20.

**Never charged:** blank lines, empty-cell placeholders (`N/A`, `NA`, `null`, `None`, `nan`, `undefined`, `-`, `--`,
`#N/A`, `#VALUE!`, `0`, `true` / `false` and similar), and lines over 1,000 characters. They still get a row (with
`EMPTY`, `PLACEHOLDER` or `TOO_LONG`) so your output lines up with your input. Set "Maximum cost per run" to cap
spending: the run stops cleanly at that limit and never bills beyond it.

### Limits and what is not verified

- **25,000 addresses per run** (free-text and structured together) and **1,000 characters per address**. A larger
  list is rejected up front with a "split the list" message, and nothing is charged.
- **Structure and PIN directory, not delivery.** Sthana does not geocode, does not confirm that a house or street
  exists, and does not know whether a courier serves the PIN. `valid: true` means the address is complete and
  consistent with the India Post directory.
- **PIN directory version**: India Post "All India Pincode Directory", as of 2025-10-03 (19,586 PINs, 165,593 post
  office records, 749 districts). The Docker build downloads the current file from data.gov.in and falls back to this
  bundled snapshot when data.gov.in is unreachable; the version used is in the run log and the `SUMMARY` record. PINs
  created after that date show as `PIN_NOT_FOUND`.
- **Army Postal Service PINs** (9xxxxx) are not in the public directory: `PIN_ARMY_POSTAL` warning, `valid: false`.
- **City names**: about 90 major cities with their historic names and spellings are recognised, plus every India
  Post district name. Other towns and villages are kept as written (with `CITY_NOT_FOUND` as info when no city is
  recognised; locality, tehsil and district are given instead).
- **District names are India Post's spellings**, which can differ from everyday usage or lag behind a renaming
  (`Ahmadabad`, `24 Paraganas North`).
- **Locality checks need a post office of that name.** A locality without its own post office cannot be matched to a
  PIN, so a wrong PIN there is not caught.
- **Non-Latin text** is kept as written, not transliterated. City and state names in Hindi (and a few other scripts)
  are recognised; other words in those scripts are labelled by position only.
- **Indian addresses only.** An address in another country is reported as `COUNTRY_NOT_INDIA` and never relabelled
  "India".
- The `input` field echoes what you sent, so a phone number typed into an address is still visible there; every
  derived field and `formatted` leave it out.

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

Sthana works well as a tool for AI agents: the output is deterministic and every problem has a code.

- **Claude (Settings > Connectors), Claude Code, Cursor and other MCP clients:** add the Apify MCP server
  `https://mcp.apify.com?tools=motivating_bracelet/sthana-anantasutra` and sign in with your Apify account, or run it
  locally with `npx -y @apify/actors-mcp-server --actors motivating_bracelet/sthana-anantasutra` and your Apify API token
  in the `APIFY_TOKEN` environment variable.
- **ChatGPT:** add `https://mcp.apify.com` as a custom connector (MCP) where your plan allows it.

Then ask in plain language, for example: *"Clean these delivery addresses with motivating_bracelet/sthana-anantasutra and
list the ones whose PIN does not match the city."* 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~sthana-anantasutra/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"addresses": ["Flat 5, MG Road, Pune 411001", "Koramangala, Bangalore 560034"]}'
```

### FAQ

**Does `valid: true` mean the parcel will be delivered?** No. It means the address has a place name and a PIN that
exists, and nothing in it contradicts the India Post directory. It cannot tell whether flat 302 exists.

**Why was my town not changed to the district's big city?** On purpose. A district such as Kanchipuram or Raigad is
not another name for Chennai or Navi Mumbai, and printing the wrong city on a label causes misroutes. Only real
aliases (Bombay, Madras, Gurgaon, ...) are renamed, and every change is listed in `issues`.

**My old address says "Hyderabad, Andhra Pradesh". Is it rejected?** No. The state is updated to Telangana with a
`STATE_MODERNIZED` warning and the address stays valid. The same applies to Leh or Kargil written with Jammu and
Kashmir (now Ladakh) and to the states created in 2000 (Jharkhand, Chhattisgarh, Uttarakhand).

**The PIN is "400-021" or "Delhi 92". What happens?** `400-021` is read as 400021. `Delhi 92` is old postal-zone
shorthand: the row gets `PIN_INVALID_FORMAT`, the city stays Delhi, and 110092 is the first suggested PIN.

**Do Hindi addresses work?** Yes. Devanagari digits, Hindi state and city names, the abbreviations `उ.प्र.`, `म.प्र.` and `हि.प्र.`, and Hindi markers such as `मकान नं`
and `के पास` are understood. Hindi words are kept as written, and city and state come out in English.

**Is my data sent anywhere?** No. Addresses are processed inside the Actor run and saved only to your own Apify
dataset and key-value store.

**Is this an official India Post service?** No. It is an independent tool built on public data and is not affiliated
with the Department of Posts or any government body.

### 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,
  https://www.data.gov.in/Godl). *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.
- **State names and codes**: ISO 3166-2:IN (with the 2023 update) and the official names of states and union
  territories, verified 2026-10-20.
- **City names, historic names and address keywords**: compiled for this Actor from official renamings and common
  spellings; maintained with the source code.

# Actor input Schema

## `addresses` (type: `array`):

One complete address per line: free text in English, Hindi or a mix. Max 25,000 per run. Phone numbers inside an address are left out of the result; blank and placeholder lines (N/A, null, -) are free.

## `structuredAddresses` (type: `array`):

Addresses you already hold as fields, e.g. from a checkout or marketplace export: \[{"address1": "Flat 4, Sunshine Residency", "address2": "HSR Layout", "city": "Bangalore", "province": "Karnataka", "zip": "560102"}]. Field names from Shopify, WooCommerce, Amazon and similar exports are understood (line1-3, address1-3, address_1, AddressLine1, street, locality, area, landmark, city, district, state, province, StateOrRegion, pincode, pin, zip, postcode, PostalCode, country). Fields that are not used are listed in an UNKNOWN_FIELD issue. A country other than India is reported as COUNTRY_NOT_INDIA and the row is not marked valid. Their rows follow the free-text addresses.

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

Title Case is easiest to read; UPPER CASE suits courier labels; 'As written' keeps the original letters.

## `includePincodeDetails` (type: `boolean`):

Add the district(s), state and post offices of each address's PIN code to the result.

## `suggestPincode` (type: `boolean`):

When the PIN is missing, malformed or not in the directory, suggest PIN codes whose post office matches the locality written in the address.

## Actor input object example

```json
{
  "addresses": [
    "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
    "#45, 2nd Cross, 4th Block, Koramangala, Bangalore 560034, KA",
    "B-12, Sector 62, Noida, UP 201301",
    "At Post Dadh BK, Tal. Rahata, Dist. Ahmednagar, Maharashtra 413714",
    "मकान नं 12, गांधी नगर, जयपुर, राजस्थान 302001",
    "Flat 5, MG Road, Mumbai, Karnataka 560001",
    "Andheri East Mumbai"
  ],
  "outputCase": "title",
  "includePincodeDetails": false,
  "suggestPincode": true
}
```

# Actor output Schema

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

Dataset items, one per input address.

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

The same dataset with every parsed address component.

# 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 = {
    "addresses": [
        "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
        "#45, 2nd Cross, 4th Block, Koramangala, Bangalore 560034, KA",
        "B-12, Sector 62, Noida, UP 201301",
        "At Post Dadh BK, Tal. Rahata, Dist. Ahmednagar, Maharashtra 413714",
        "मकान नं 12, गांधी नगर, जयपुर, राजस्थान 302001",
        "Flat 5, MG Road, Mumbai, Karnataka 560001",
        "Andheri East Mumbai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("motivating_bracelet/sthana-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 = { "addresses": [
        "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
        "#45, 2nd Cross, 4th Block, Koramangala, Bangalore 560034, KA",
        "B-12, Sector 62, Noida, UP 201301",
        "At Post Dadh BK, Tal. Rahata, Dist. Ahmednagar, Maharashtra 413714",
        "मकान नं 12, गांधी नगर, जयपुर, राजस्थान 302001",
        "Flat 5, MG Road, Mumbai, Karnataka 560001",
        "Andheri East Mumbai",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("motivating_bracelet/sthana-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 '{
  "addresses": [
    "Flat 302, Sai Krupa Apts, Near Hanuman Mandir, MG Road, Andheri (East), Bombay - 400069",
    "#45, 2nd Cross, 4th Block, Koramangala, Bangalore 560034, KA",
    "B-12, Sector 62, Noida, UP 201301",
    "At Post Dadh BK, Tal. Rahata, Dist. Ahmednagar, Maharashtra 413714",
    "मकान नं 12, गांधी नगर, जयपुर, राजस्थान 302001",
    "Flat 5, MG Road, Mumbai, Karnataka 560001",
    "Andheri East Mumbai"
  ]
}' |
apify call motivating_bracelet/sthana-anantasutra --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,motivating_bracelet/sthana-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/5hh5vHWpYgW7ymdpK/builds/AL3eF4M62gg8Spu9m/openapi.json
