We run an IP law practice. At some point our internal tooling needed to search the Indian Trade Marks Registry programmatically, so we scraped it — 3.3M+ records, going back to Trade Marks Journal issue 1703. Someone building a brand-clearance tool asked if that data was available as an API. It wasn't, so we shipped one.
This isn't a "look at our product" post. It's what the underlying data source actually does to you when you try to read it at scale, plus the API we ended up with.
The registry does not want to be scraped
A few specific things broke us, in no particular order:
Inconsistent filenames across journal issues. The Trade Marks Journal (currently in the 2200s) uses two different filename conventions depending roughly on when the issue was published — no documented boundary, no changelog. Guess wrong and you get a
500with an HTML error page, not a404. If you're not checkingcontent-typebefore parsing, that HTML silently becomes garbage input to your parser.A silent truncation bug that looked like a livelock. Our journal backfill stopped advancing one day. We spent real time assuming a deadlock in the crawler's concurrency logic. The actual cause: our HTTP client capped response bodies at 2MB, and the listing page for certain issues needed ~2.3MB. No error, no crash — just fewer rows than existed, forever, until someone diffed the counts against the portal manually.
A "captcha" that's just a JSON endpoint. The live trademark-status lookup gates behind what looks like a captcha challenge. Read what the frontend actually calls, though, and it's a plain JSON request-response — no headless browser, no image solving needed. Worth remembering before you reach for Selenium as a default.
The takeaway if you're scraping any government portal: it won't fail loudly. It'll hand you 90% of the data and let you find out about the missing 10% on your own time.
What we shipped
Two read-only GET endpoints. Deliberately not more than that.
1. Search by name / class
curl -H "X-API-Key: $TRADEMARX_API_KEY" \
"https://admin.trademarx.in/api/public/v1/trademarks/search?name=chai&class=30"
2. Pull one Trade Marks Journal issue
curl -H "X-API-Key: $TRADEMARX_API_KEY" \
"https://admin.trademarx.in/api/public/v1/trademarks/journal?journalNo=2145"
Both return a JSON array, no envelope object. Pagination metadata lives in headers, not the body:
X-Total-Count: 4213
Link: <...&page=1>; rel="next", <...&page=49>; rel="last"
A minimal Python client, stdlib only:
import os, urllib.request, json
key = os.environ["TRADEMARX_API_KEY"]
req = urllib.request.Request(
"https://admin.trademarx.in/api/public/v1/trademarks/search?name=nike&class=25",
headers={"X-API-Key": key},
)
with urllib.request.urlopen(req) as resp:
data = json.load(resp)
print(f"{len(data)} results, {resp.headers['X-Total-Count']} total")
Response shape, and where it'll bite you
{
"name": "NIKE SWIFT",
"tmClass": 25,
"applicationNo": 6765007,
"proprietorName": "NIKE INNOVATE C.V.",
"trademarkStatus": "Registered",
"imgUrl": "https://admin.trademarx.in/files/2145-41-6303904.jpg",
"url": "https://trademarx.in/trademarks/nike-swift-class-25-6765007"
}
Only three fields are guaranteed non-null: applicationNo, tmClass, url. Everything else can be null, and that's not a formality:
-
nameis null for a real chunk of the register — device/figurative marks with no word element. Key onapplicationNo, notname. -
trademarkStatusis null for most records, because the registry hasn't published a status, not because we dropped it. Don't render that as"Unknown"in a UI where someone might read it as a legal fact. -
trademarkStatusis free text with no fixed vocabulary —"Registered","Abandoned","Formalities Chk Pass", whatever the registry typed that day. Match loosely, don't switch on exact strings.
Limits, on purpose
| Thing | Limit |
|---|---|
| Requests/day, per endpoint group | 200 |
| Page size | 20 (capped, even if you ask for more) |
| Max pages | 50 (page 0–49) |
| Deepest reachable record | 1,000 (narrow your query instead of paging past this) |
No bulk export, no POST. That's deliberate — a free tier that also does bulk dumps just becomes someone else's dataset business subsidized by us. If you need bulk, email us.
Auth is boringly simple
One header, no expiry, no OAuth dance:
X-API-Key: tmx_live_xxxxxxxxxxxx
CORS is open on /api/public/v1/**, so it works straight from browser JS for a demo. Don't ship a production app with the key in client-side code, though — anyone with devtools open can read it. Proxy through your own backend once it's real.
The one condition
It's free in exchange for an attribution link staying visible on your registered domain — checked weekly, warns before it suspends. Every response also carries a url field pointing at the canonical page for that record, which doubles as a natural per-result attribution link if you'd rather do it that way than a single site-wide badge.
Spec + getting a key
- OpenAPI 3.1 spec, generated from the live controllers so it can't drift:
https://trademarx.in/openapi.json - Keys, instant, no approval queue:
https://trademarx.in/developers
Curious if anyone here has dealt with similarly inconsistent government data sources — the filename-scheme-changes-with-no-notice thing feels like it can't be unique to Indian trademark filings.
Top comments (0)