Trademarx - A free public API for Indian Trademarks

dev.to

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 500 with an HTML error page, not a 404. If you're not checking content-type before 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

Only three fields are guaranteed non-null: applicationNo, tmClass, url. Everything else can be null, and that's not a formality:

  • name is null for a real chunk of the register — device/figurative marks with no word element. Key on applicationNo, not name.
  • trademarkStatus is 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.
  • trademarkStatus is 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
Enter fullscreen mode Exit fullscreen mode

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.

Source: dev.to

arrow_back Back to News