API Documentation
v1One key, one header, and every response in the same shape. Most people are making live calls within five minutes.
- Base URL
- https://www.gstinapi.in
- Authentication
- x-api-key: gak_…
- Rate limit
- 60 req / min
- Format
- JSON · HTTPS
Environment
Sandbox pre-fills the free test GSTIN and turns on test_mode for e-way bill / e-invoice calls, so nothing is charged or filed. Live uses your own data and credits. Applies to every console and code sample below.
All endpoints & sections
Start here#
- 1
Get your key
It is on the API Keys page. New accounts get up to 75 free credits — 10 on signup, 65 more across the setup steps — no card needed, and they never expire.
- 2
Make one call
Paste this in a terminal, with your key swapped in.
bashcurl "https://www.gstinapi.in/v1/gstin/33AAACC1206D1ZN" \ -H "x-api-key: gak_your_api_key_here" - 3
Branch on
successIt is
trueorfalseon every response, success or failure. That is the only check your code strictly needs.
What you can ask
Is this GSTIN real, and who is behind it?
Legal name, trade name, status, taxpayer type, registered address.
GET /v1/gstin/{gstin}Have they been filing their returns?
Every GSTR-1 and GSTR-3B filed in a financial year, with dates and ARNs.
GET /v1/gstin/{gstin}/returnsDo they file monthly or quarterly?
QRMP preference, quarter by quarter.
GET /v1/gstin/{gstin}/filing-preferenceAre they filing on time?
Filed counts, last filed period, and average filing delay — per return type.
GET /v1/gstin/{gstin}/complianceCan I get all of this in one call?
Verification, return history and filing frequency together, for 2 credits instead of 3.
GET /v1/gstin/{gstin}/completeTell me when a vendor's GSTIN changes
Put it on a watchlist. We re-check daily, weekly or monthly and call your webhook on any change.
POST /v1/watchlistsWhat does this HSN or SAC code mean?
Official description for any 2–8 digit code, plus the chapter and heading above it.
GET /v1/hsn/{code}How many credits have I used today?
Your own usage. Free — this one never costs a credit.
GET /v1/gstin/stats/meIntegrate using AI — no coding needed
Copy the prompt below and paste it into ChatGPT, Claude, Cursor, GitHub Copilot or any AI coding assistant. It contains everything needed to write a working integration — you only have to say what your project is.
- Click Copy prompt, then paste it into your AI assistant.
- Replace
MY PROJECTwith what you are building. - Paste your API key where the assistant tells you to — take it from the API Keys page.
I want to add GSTIN (Indian GST number) verification to my project using the
gstinapi.in REST API. Please write the integration code for me.
MY PROJECT: <describe it — e.g. "a Node.js Express app", "a WordPress site",
"a Google Sheet", "a Python script", "a React form">
IF MY PROJECT IS NODE.JS OR PYTHON, USE THE OFFICIAL LIBRARY INSTEAD OF RAW HTTP
Node.js : npm install gstinapi -> const { GstinApi } = require('gstinapi')
Python : pip install gstinapi -> from gstinapi import GstinApi
Both take { apiKey } / (api_key=...), expose .lookup(gstin), and already handle
the retry rules and checksum validation described below. For any other language,
call the REST endpoint directly using the details below.
API DETAILS
Base URL : https://www.gstinapi.in
Endpoint : GET /v1/gstin/{gstin}
Auth : header x-api-key: <MY_API_KEY>
Rate limit : 60 requests per minute per key
EXAMPLE REQUEST
curl "https://www.gstinapi.in/v1/gstin/33AAACC1206D1ZN" -H "x-api-key: MY_API_KEY"
SUCCESS RESPONSE (HTTP 200)
{
"success": true,
"gstin": "33AAACC1206D1ZN",
"credits_remaining": 487,
"response_ms": 312,
"data": {
"gstin": "33AAACC1206D1ZN",
"legal_name": "CENTRAL WAREHOUSING CORPORATION",
"trade_name": "CENTRAL WAREHOUSING CORPORATION",
"status": "Active",
"taxpayer_type": "Regular",
"business_constitution": null,
"registration_date": "2017-07-01",
"cancellation_date": null,
"state_code": "33",
"state_jurisdiction": null,
"address": "No.4, Thiruvalar Illam, North Avenue, Srinagar Colony, Saidapet, Chennai",
"city": "Saidapet, Chennai",
"address_details": {
"building_number": "No.4",
"building_name": "Thiruvalar Illam",
"floor": null,
"street": "North Avenue",
"locality": "Srinagar Colony, Saidapet, Chennai",
"district": null,
"city": null,
"state": null,
"landmark": null,
"pincode": "600015"
},
"pincode": "600015",
"nature_of_business": null,
"block_status": "Unblocked"
}
}
ERROR RESPONSE — same shape for every failure
{ "success": false, "error": "human readable reason" }
STATUS CODES
200 success, 1 credit charged
400 invalid GSTIN format — no credit charged
401 missing or invalid x-api-key
402 out of credits
403 account deactivated
404 GSTIN not registered in the GST database
429 rate limit exceeded
502 GST provider temporarily unavailable — safe to retry
REQUIREMENTS
1. Read the API key from an environment variable. Never hardcode it, and never
expose it in browser/client-side code — call the API from my server.
2. Validate the format before calling so a typo does not waste a request:
^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$
Uppercase the input first.
3. Handle every status code above with a clear message for the end user.
Treat 404 (GSTIN does not exist) and 402 (out of credits) as different things.
4. On 429 and 502 only, retry with exponential backoff, maximum 3 attempts.
Never retry a 400/401/402/403/404.
5. business_constitution, state_jurisdiction and nature_of_business are null
on the basic lookup. Append ?include=profile to populate them (same credit
cost) — that also adds einvoice_status ("Yes"/"No"), jurisdiction codes,
last_updated and additional_addresses, plus a top-level profile_complete
boolean.
6. city is the registered address's locality as the GST network records it,
not always a clean single city name — it can be a locality within a metro
(e.g. the example above returns "Saidapet, Chennai", not just "Chennai"),
and can itself contain more than one comma-separated part. Do not treat it
as a strict city-only field without validating against your own address
data.
7. Only the principal place of business is returned. GSTINs with multiple
registered addresses (additional places of business) are not covered by
this endpoint.
8. address_details is the same address split into parts, for filling a
structured address form. Use it instead of parsing the address string.
Any part the GST network has no value for is null — floor, landmark and
district are blank for a lot of real registrations, so render around the
nulls rather than assuming every part is present. district and city are
filled only with ?include=profile. There is no latitude/longitude: the
GST network does not publish coordinates for a registered address.
9. Log credits_remaining after each call so I can watch my balance.
Tell me exactly which file to create, where to put my API key, and how to run it.Your API key is a password — keep it on your server. Anyone who has it can spend your credits.
Official client libraries#
Zero dependencies, typed, and they verify the GSTIN checksum locally — so a mistyped number fails instantly instead of spending a credit on a request that cannot succeed.
npm install gstinapiconst { GstinApi } = require('gstinapi');
const client = new GstinApi({ apiKey: process.env.GSTIN_API_KEY });
const result = await client.lookup('33AAACC1206D1ZN');
console.log(result.data.legal_name); // CENTRAL WAREHOUSING CORPORATION
console.log(result.credits_remaining); // 9Using another language? Every example below is plain HTTP — no SDK required.
Step-by-step integration guides
One language each, start to finish — including validating a GSTIN offline so a typo never costs a credit.
Google Sheets#
Check GST numbers straight from a spreadsheet. No code and no server: paste one script into your sheet and use a formula.
=GSTIN(A2) legal name
=GSTIN(A2, "status") Active / Cancelled / Suspended
=GSTIN(A2:A100, "status") a whole column in one formula
=GSTIN(A2, "all") name, trade name, status, type, registered on, state, pincode
=GSTIN_VALID(A2) TRUE or FALSE, offline and free- Create a free key on the sign-up page, then copy it from API keys.
- In your sheet, open Extensions, then Apps Script.
- Replace the contents of Code.gs with this script and save.
- Reload the sheet. In the new GSTIN API menu choose Set API key and paste your key. Google asks you to authorise the script once.
One credit per distinct GSTIN that exists. A wrong format or a wrong check character is caught inside the sheet, so it is free; not-found and failed lookups are free too. If a cell still shows NO_KEY after you set the key, click it and press Enter.
Base URL#
https://www.gstinapi.inAuthentication#
Pass your API key in every request using the x-api-key header.
x-api-key: gak_your_api_key_hereYou can also pass it as a query param: ?api_key=gak_... (less secure)
Endpoints
GSTIN lookups
Everything about a GST registration, from one key. A successful lookup costs one credit; a malformed GSTIN, an unregistered one, or a provider outage costs nothing.
Endpoints
Verify a GSTIN#
https://www.gstinapi.in/v1/gstin/{gstin}The main endpoint: who a GSTIN belongs to, and whether it is still active. Try it in the browser first →
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
Verify a GSTIN
Code Examples#
curl -X GET \
"https://www.gstinapi.in/v1/gstin/22AAAAA0000A1Z5" \
-H "x-api-key: gak_your_api_key_here"Verify a GSTIN
Success Response#
{
"success": true,
"gstin": "22AAAAA0000A1Z5",
"credits_remaining": 487,
"response_ms": 312,
"data": {
"gstin": "22AAAAA0000A1Z5",
"legal_name": "EXAMPLE PRIVATE LIMITED",
"trade_name": "EXAMPLE PVT LTD",
"status": "Active",
"taxpayer_type": "Regular",
"business_constitution": null,
"registration_date": "2017-07-01",
"cancellation_date": null,
"state_code": "22",
"state_jurisdiction": null,
"address": "SHOP NO. 12, 1ST FLOOR, 123 BUSINESS PARK, RAIPUR",
"city": "RAIPUR",
"address_details": {
"building_number": "SHOP NO. 12",
"building_name": "123 BUSINESS PARK",
"floor": "1ST FLOOR",
"street": null,
"locality": "RAIPUR",
"district": null,
"city": null,
"state": null,
"landmark": null,
"pincode": "492001"
},
"pincode": "492001",
"nature_of_business": null,
"block_status": "Unblocked"
}
}The data schema is stable and identical for every lookup. Fields not available for a given GSTIN are returned as null. Dates are ISO format (YYYY-MM-DD). address and city only cover the principal place of business — additional registered addresses under the same GSTIN are not returned. city is the locality as the GST network records it, not a normalized city name — for a metro address it is often a locality within the city (for example "Saidapet, Chennai", not just "Chennai") and can itself contain more than one comma-separated part.
monitoring_hint appears only when this key has already looked up the same GSTIN before and is not watching it — { gstin, times_checked, message, url }. It means you are polling a vendor by hand; a watchlist re-checks it for you and emails you on change (see Watchlists). Absent otherwise; safe to ignore.
Building a structured address form? Use address_details
Every lookup also returns address_details — the same principal place of business split the way the GST network itself stores it: building_number, building_name, floor, street, locality, district, city, state, landmark, pincode. No extra credit, and address is unchanged — use whichever suits you.
Every key is always present, so the shape never varies. A null part means the GST network holds no value for it, not that we dropped it — floor, landmark and district are blank for a large share of real registrations, so design the form around missing parts. district and city are filled only when you pass ?include=profile. Latitude and longitude are not available: the GST network does not publish coordinates for a registered address, and we do not geocode.
Null on the basic lookup: business_constitution, state_jurisdiction and nature_of_business. The keys are always present so the schema never changes. Add ?include=profile (below) to populate them.
Want more detail? Add ?include=profile
Same endpoint, same credit — the response additionally carries the taxpayer’s profile straight from the GST network. It fills the three null fields above and adds six more:
| einvoice_status | “Yes” / “No” — whether this taxpayer is enabled for e-invoicing. Useful before accepting a B2B invoice without an IRN. |
| state_jurisdiction_code | e.g. “GJ068” |
| centre_jurisdiction | Centre range, e.g. “RANGE-IV” |
| centre_jurisdiction_code | e.g. “VC0104” |
| last_updated | When the GST network last updated this registration record |
| additional_addresses | Additional places of business, when registered |
It also fills address_details.district and address_details.city, which the basic lookup leaves null — its source has no field for either.
The response then carries profile_complete: true. If the profile source is momentarily unavailable, the verification still succeeds — the extra keys come back as null and profile_complete is false.
Verify a GSTIN
Error Response#
Every failure returns the same shape, so you can branch on success and surface error directly.
{
"success": false,
"error": "Invalid GSTIN format. Must be 15 characters in pattern: 22AAAAA0000A1Z5"
}Endpoints
Return Filing Status#
https://www.gstinapi.in/v1/gstin/{gstin}/returnsLast filed return period, type, status and date for a GSTIN, one entry per return filed in the given financial year. Same auth, credits and rate limit as the main endpoint.
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
| fy | string, required | Financial year, format YYYY-YY (e.g. 2025-26) |
| type | string, optional | Filter to one return type, e.g. R1 for GSTR-1 |
return_period is YYYY-MM, the month the return covers. An empty returns array means no return was filed for that GSTIN in that financial year. For whether a GSTIN files Monthly or Quarterly, use the dedicated /v1/gstin/{gstin}/filing-preference endpoint below.
Endpoints
Return Filing Frequency#
https://www.gstinapi.in/v1/gstin/{gstin}/filing-preferenceWhether a GSTIN is on Monthly or Quarterly (QRMP) return filing, one entry per quarter of the financial year — a taxpayer can switch quarter to quarter, so this is not a single fixed value. Same auth, credits and rate limit as the main endpoint.
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
| fy | string, required | Financial year, format YYYY-YY (e.g. 2025-26) |
Endpoints
Complete Profile (bundle)#
https://www.gstinapi.in/v1/gstin/{gstin}/completeVerification, return filing history and filing frequency in one call, for 2 credits — cheaper than calling /v1/gstin/{gstin}, /returns and /filing-preference separately (3 credits total). Best-effort on filing history and filing frequency: if either cannot be fetched, those fields come back null rather than failing the whole call — you are only ever charged once verification itself succeeds.
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
Only need one field? /v1/gstin/{gstin}/returns and /v1/gstin/{gstin}/filing-preference above are each 1 credit on their own — use this bundle when you want all three together.
Endpoints
Filing Compliance Summary#
https://www.gstinapi.in/v1/gstin/{gstin}/complianceThe filing history, already summarised: how many returns of each type were filed in the financial year, the last period filed, how many months ago that was, and how long after each period’s end the taxpayer typically files. One call instead of fetching/returnsand computing it yourself.
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
| fy | string, required | Financial year, format YYYY-YY (e.g. 2025-26) |
avg_filing_lag_days is measured from the end of the return period, not the statutory due date — it is a habit indicator, not a legal late-filing count. A quarterly (QRMP) filer legitimately shows 2–3 month gaps between GSTR-1 periods, so apply your own thresholds with the taxpayer’s filing frequency in mind.
Endpoints
Registered Mobile and Email#
https://www.gstinapi.in/v1/gstin/{gstin}/contactThe mobile number and email registered against a GSTIN, together with its legal name, status, address, city, state and PIN code read live from the GST network, so you can see whose contact it is. The GST portal’s public search does not show the contact. The same lookup as the Contact option of Bulk GSTIN Check, one GSTIN per call.
- Paid accounts only: any credit pack or invoice order enables it. Every account can call the test GSTIN 00AAAAA0000A1ZT for a free sample.
- Price by your best pack: 4 credits on Scale or Business, 6 on Starter. credits_charged in each response says what this call cost.
- A GSTIN with no contact on record is charged, because the lookup ran (contact_found is false and note says so). Bad format, not-found GSTINs and provider errors are free.
- Up to 500 charged lookups per account per day, reset at midnight India time; daily_limit in each response shows where you are. For a long list, upload it to Bulk GSTIN Check instead.
| Parameter | Type | Description |
|---|---|---|
| gstin | string | 15-character GSTIN (case insensitive) |
Response fields
| Field | Description |
|---|---|
| data.legal_name … data.address | Registration details from the live GST network, as in the verify endpoint. Null when that lookup failed; gst_details_available is then false. |
| data.mobile, data.email | The contact on the registration record, or null when there is none |
| contact_found | true when a mobile or email came back |
| note | Why something is empty: "No contact on record", "GST details temporarily unavailable", or both. Null when everything came back. |
| credits_charged | What this call cost (your pack rate) |
| daily_limit | limit, used and remaining charged lookups for today (India time) |
The contact is the one on the GST registration, which may be a consultant’s, or out of date if the business changed its number without amending its registration. A mobile number and email are personal data under Indian law: use them for the business purpose you looked them up for, such as vendor onboarding, know-your-business checks or collections, and not for unsolicited marketing. More on this: GSTIN to contact details.
Endpoints
Usage Stats#
https://www.gstinapi.in/v1/gstin/stats/meToday’s usage for the calling API key. Authenticated the same way, and does not consume a credit.
No 400/404/429/502 on this endpoint — there is no input to validate, no upstream call, and no rate limiter here.
PAN lookup
Find every GSTIN under a PAN
The GST portal has a manual Search by PAN page but we found no public API for it. This checks every GST state code a PAN could be registered under, through the same live lookup /v1/gstin/{gstin} uses, and returns only the GSTINs that actually exist.
PAN lookup
Find GSTINs by PAN#
https://www.gstinapi.in/v1/pan/{pan}/gstinsA GSTIN embeds its PAN, but there is no shortcut that goes the other way. We build every mathematically valid GSTIN the PAN could hold — one per GST state code, at the first registration index — and check each one against the live network. Most PANs are registered in only a handful of states, so a not_found from most of the 36 states checked is the ordinary outcome, not an error. Only entity code “1” (the first registration) is tried per state; a second registration in the same state under the same PAN is rare enough that checking for it is not worth 35x more upstream calls by default.
Billed once per scan (1 PAN search credit, not per state checked), regardless of how many GSTINs are found — including zero.
A separate wallet from GSTIN lookup credits. This endpoint reads and spends onlyusers.pan_credits, bought as its own packs at/buy-credits#pan-search. Buying GSTIN lookup credits never adds PAN search credits, and buying PAN search credits never adds GSTIN lookup credits — an account can be well funded on one and empty on the other.
36 upstream calls per request. This endpoint answers in well under a second because those calls run in parallel, but it is still meaningfully heavier than a single GSTIN lookup — rate-limited to 5 requests a minute per key, separate from the 60/min limit on/v1/gstin.
PAN lookup
Verify a PAN#
https://www.gstinapi.in/v1/pan/{pan}/verifyChecks that a PAN exists and returns the registered name of its holder. Pick how much you need with level: basic (the registered name, the default), verified (also date of birth, holder category and whether an Aadhaar is linked) or details (also address and contact, as the source returns them). The masked Aadhaar number is never returned. Date of birth is returned as DD/MM/YYYY. The credit price depends on the level and your pack, from the regular credit balance. Nothing is cached.
| Level | Scale or Business pack | Starter pack |
|---|---|---|
| basic | 4 credits | 6 credits |
| verified | 6 credits | 10 credits |
| details | 12 credits | 18 credits |
Monitoring & Webhooks
Watch GSTINs for changes
Instead of re-checking vendors by hand, put them on a watchlist. We re-check each one daily, weekly or monthly and deliver a signed webhook within the hour of any change.
Monitoring & Webhooks
Watchlists#
https://www.gstinapi.in/v1/watchlistsA watchlist is a named group of GSTINs checked on a shared schedule —daily,weekly ormonthly. Create as many as you like; monitoring is priced per distinct GSTIN watched across your account, not per list. Every route on this page accepts the samex-api-keyheader as GSTIN lookups.
GET /v1/watchlists lists yours (withentry_count and 30-day critical-alert count on each).PATCH /v1/watchlists/:id renames, reschedules, pauses or resumes one. DELETE /v1/watchlists/:id removes it and every entry on it.
Monitoring & Webhooks
Add GSTINs to a watchlist#
https://www.gstinapi.in/v1/watchlists/{id}/entriesSend one GSTIN or an array of them. A GSTIN already on another one of your watchlists adds for free — you only pay quota for distinct GSTINs across the whole account.vendor_code andlabel are optional free text, yours to key back to your own ERP or CRM.
Up to 5,000 rows per request. Bad rows are reported individually and do not block the good ones in the same request. Over your plan’s quota, the whole request is refused with402 and the response carries your current entitlement (used / quota) rather than a bare error.
Bulk import also accepts a CSV body atPOST /v1/watchlists/:id/entries/import(content-type: text/csv, columnsgstin,vendor_code,label, header row optional).
Monitoring & Webhooks
Remove a GSTIN from a watchlist#
https://www.gstinapi.in/v1/watchlists/{id}/entries/{entryId}entryId is the id returned by GET /v1/watchlists/:id/entries, not the GSTIN itself.
Monitoring & Webhooks
Change timeline#
https://www.gstinapi.in/v1/watchlists/{id}/eventsEvery detected change for GSTINs on this watchlist — a status flip, a filing gone stale, an address change. Optional severity andsince query params filter it.
severity is one ofinfo, medium,high, critical. A CSV export of the same data is atGET /v1/watchlists/:id/events.csv.
Monitoring & Webhooks
Register a webhook#
https://www.gstinapi.in/v1/webhooksRather than polling /events, register an HTTPS endpoint and we POST to it as soon as changes are detected — typically within an hour. This is what you would point at an n8n, Zapier or Make webhook trigger.
Must be HTTPS and publicly reachable — no localhost or private IPs. Maximum 3 endpoints per account. Save the secret: you will need it to verify incoming payloads. GET /v1/webhooks lists your endpoints (the secret is returned again on every read), DELETE /v1/webhooks/:id removes one, and POST /v1/webhooks/:id/test fires a signed sample event at it immediately — for testing your receiver before real data flows.
Webhook payload & signature#
Every delivery is a signed POST:
POST <your url>
Content-Type: application/json
X-Gstinapi-Event: monitoring.changes
X-Gstinapi-Timestamp: 1767520000
X-Gstinapi-Signature: sha256=7d8f3a...{
"type": "monitoring.changes",
"sent_at": "2026-09-04T10:15:00.000Z",
"events": [
{
"id": 4821,
"gstin": "27AAPFU0939F1ZV",
"event_type": "status_changed",
"severity": "critical",
"old_value": "Active",
"new_value": "Cancelled",
"detected_at": "2026-09-03 14:05:00",
"watchlist": "CA Firm Clients",
"vendor_code": "V-1001",
"label": "Sharma Traders"
}
]
}Up to 75 events per delivery; more pending events arrive in the next batch. Possibleevent_type values: status_changed,taxpayer_type_changed, block_status_changed,cancellation_date_changed, legal_name_changed,trade_name_changed, address_changed,monitoring_started, filing_band_changed.
Verify it with your endpoint’s secret (Node.js):
const crypto = require('crypto');
function verify(secret, timestamp, rawBody, signatureHeader) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + rawBody)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}Use the raw request body, before JSON parsing — the signature is computed over the exact bytes sent. We only mark an event delivered once your endpoint answers 2xx; anything else leaves it pending and the next hourly delivery run retries the whole backlog, so a receiver that was down for a few hours gets everything it missed as soon as it is back. After 24 consecutive failures (roughly a day at the hourly cadence) the endpoint is auto-disabled.
HSN / SAC codes
Classification codes
Official descriptions for goods (HSN) and services (SAC) codes, served from a local copy of the GST portal master in about a millisecond. Descriptions and hierarchy only — no tax rate, because a rate is not a function of the code alone.
HSN / SAC codes
Look up an HSN / SAC code#
https://www.gstinapi.in/v1/hsn/{code}The official description for any 2 to 8 digit HSN (goods) or SAC (services) code, plus the broader codes it sits under so you can show a breadcrumb without extra requests. Served from our own copy of the GST portal’s published master — typically under 50 ms.
kind is hsn for goods or sac for services. A malformed code is a 400 and an unknown one a 404 — neither is charged. Every response carriessource.refreshed_atso you can see how current the master is.
No GST rate is returned, deliberately. A rate is not a function of the code alone — the same HSN attracts different rates by condition (branded vs unbranded, price thresholds, end use) under different notifications. The government’s own master publishes no rate column either. We would rather return nothing than a number that could end up wrong in your return.
HSN / SAC codes
Search HSN / SAC by description#
https://www.gstinapi.in/v1/hsn/searchFind a code when you only know the goods or service. An exact code match ranks first, then descriptions starting with your term, then descriptions containing it — so a search for “cotton” surfaces the cotton chapter before every item that merely mentions it.
| Parameter | Type | Description |
|---|---|---|
| q | string, required | At least 2 characters. Matched against both code and description. |
| kind | string, optional | hsn for goods or sac for services |
| limit | integer, optional | Default 20, maximum 100 |
E-way bill
Generate and manage e-way bills
File a real e-way bill with the government network, then keep it current while the goods move: update the vehicle or transporter, extend its validity, cancel or close it. gstinapi.in owns the credit charge here; the filing itself happens on that network.
E-way bill
Test portal credentials#
https://www.gstinapi.in/v1/ewaybill/credentials/testLogs in with your portal API user and does nothing else: no document is created and nothing is charged. Run it once before your first live call; most "Invalid login credentials" (error 108) failures are an API user created for a different GSP. For e-invoice, POST the same body to /v1/einvoice/credentials/test with einv_username. Limited to 3 tests per GSTIN every 15 minutes. How to create the API user: credentials setup guide.
E-way bill
Check an e-way bill payload (free)#
https://www.gstinapi.in/v1/ewaybill/validateFree: no portal credentials, no credits, and the government network is never contacted, so you can call it as often as you like. It checks the mistakes that most often get a document refused: document number and date rules, GSTIN format and state codes, pincodes, HSN, value totals, vehicle number format and missing transport details. Any signed-in key works, paid or not.
An error is a rule the government network applies without exception. A warning is a probable mistake, for example a GSTIN with a wrong check character. Passing does not guarantee acceptance: a cancelled GSTIN, a duplicate document and your portal login are only visible to the government network. When a live call is refused, the response carries a likely_causes list built by this same check, but the check never blocks a live call.
E-way bill
Generate an e-way bill#
https://www.gstinapi.in/v1/ewaybillFiles a real e-way bill against the government network and returns the e-way bill number.
Costs 3 credits per successful e-way bill — not per attempt.
- Log in to the e-way bill portal (ewaybillgst.gov.in) as the taxpayer or transporter for that GSTIN.
- Open Registration and choose For GSP.
- Select the GSP named in the credentials setup guide.
- Create the API username and password there — separate from your normal portal login.
- Check the username and password with POST /v1/ewaybill/credentials/test (free, nothing is generated). Nothing needs enabling on our side. Until the test passes, build with
test_mode.
gstin_username and password below are the API username and password from step 4, not your gstinapi.in key. Each GSTIN needs its own. Nothing you send here is stored.
| Field | Type | Description |
|---|---|---|
| gstin | string | Your GSTIN, with e-way bill API access set up |
| gstin_username | string | API username from that setup |
| password | string | API password from that setup |
| payload | object | One NIC e-way bill JSON payload |
Response fields
| Field | Description |
|---|---|
| ewaybill_no | The 12-digit e-way bill number issued by the government system |
| ewaybill_date | When it was generated, as the government system returns it |
| valid_upto | Last date the bill is valid. Empty when Part-B (vehicle) was not given: the bill is not valid for movement until you add it with update-vehicle. |
| doc_no | Your document number, echoed from the payload |
| distance_km | Distance the government system accepted for the bill |
| already_existed | true when a retry found the bill already generated for this document, instead of creating a second one (see safe retries) |
| billed_to, credits_remaining, free_remaining | What was charged and what is left |
POST /v1/ewaybill/cancel and POST /v1/ewaybill/close take the same credentials plus ewaybill_no. A request the government network rejects (expired registration, malformed payload) is never charged — the exact error_code from the filing system is returned so you know which.
E-way bill
Look up an e-way bill#
https://www.gstinapi.in/v1/ewaybill/lookupDirect lookup by document type and number — not date-scoped, so it works regardless of when the document was generated. Useful when a generate call answered “already generated” (see Safe retries below) and you want the real e-way bill number back, without guessing a date to search.
Costs 1 credit per lookup that actually reaches the government network — well under a generate call, since this is a read, not a filing. A clean "no matching document" answer is billed the same as a match, unlike a rejection elsewhere in this API, because it is still a real completed call against rate-limited upstream infrastructure. Only a request that never reaches the government network (missing fields, insufficient credits) is free.
| Field | Type | Description |
|---|---|---|
| gstin | string | Same GSTIN as generate |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| doc_type | string | NIC document type exactly as submitted at generation (e.g. INV, CHL, BIL, BOE) |
| doc_no | string | Document number exactly as submitted at generation |
E-way bill
List bills raised against my GSTIN#
https://www.gstinapi.in/v1/ewaybill/other-party/listE-way bill
Update vehicle / Part-B#
https://www.gstinapi.in/v1/ewaybill/update-vehicleUpdates the vehicle number (and related transit details) on an e-way bill already in transit — for example when the original vehicle breaks down or goods are transhipped mid-journey.
Costs 3 credits per successful update, the same price as generate/cancel/close.
| Field | Type | Description |
|---|---|---|
| gstin | string | Same GSTIN as generate |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to update |
| vehicle_no | string | New vehicle number |
| from_place | string | Place the vehicle is being updated from |
| from_state | integer | NIC state code |
| reason_code | string | NIC's own reason code — e.g. 1 Break Down, 2 Transhipment, 3 Others, 4 First Time |
| reason_remark, trans_doc_no, trans_doc_date, trans_mode | string, optional | Free-text remark and transporter document details |
E-way bill
Update transporter#
https://www.gstinapi.in/v1/ewaybill/update-transporterAssigns or changes the transporter on an e-way bill. Only the generator of the e-way bill can change its transporter — this is enforced by the government network.
Costs 3 credits per successful update, the same price as generate/cancel/close.
| Field | Type | Description |
|---|---|---|
| gstin | string | Same GSTIN as generate |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to update |
| transporter_id | string | The new transporter's GSTIN (transporter ID) |
E-way bill
Extend e-way bill validity#
https://www.gstinapi.in/v1/ewaybill/extend-validityExtends the validity of an e-way bill that is about to expire, for example when a consignment is delayed in transit. The government network only allows this within a window around expiry, and requires a Part-B (vehicle) entry to exist first — use update-vehicle beforehand if there is none.
Costs 3 credits per successful extension, the same price as generate/cancel/close.
| Field | Type | Description |
|---|---|---|
| gstin, gstin_username, password | string | Same GSTIN and credentials as generate |
| ewaybill_no | string | The e-way bill number to extend |
| from_place | string | Current place of the consignment |
| from_state | integer | NIC state code of the current place |
| from_pincode | integer | Pincode of the current place |
| remaining_distance | integer | Kilometres still to travel |
| extension_reason_code | integer | NIC's own code — 1 Natural Calamity, 2 Law & Order, 4 Transshipment, 5 Accident, 99 Others |
| extension_remark | string | Free-text reason |
| consignment_status | string | M (in movement) or T (in transit) |
| vehicle_no | string | Required when consignment_status is M |
| transit_type, trans_doc_no, trans_doc_date, trans_mode, address_line1, address_line2, address_line3 | string, optional | transit_type (R, W or O) applies when consignment_status is T; the rest are transport document and address details |
E-way bill
Get e-way bill details#
https://www.gstinapi.in/v1/ewaybill/detailsFull details of one e-way bill by its number — parties, item list, vehicle-update history. Distinct from Look up an e-way bill above, which looks up by document type + number instead of an already-known e-way bill number.
Costs 1 credit per lookup that actually reaches the government network, same as Look up an e-way bill above — a clean "no such e-way bill" answer is billed the same as a match.
| Field | Type | Description |
|---|---|---|
| gstin | string | Same GSTIN as generate |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to fetch |
E-way bill
Consolidated e-way bill#
https://www.gstinapi.in/v1/ewaybill/consolidatedCombines several e-way bills moving on one vehicle into a single consolidated e-way bill (trip sheet). You must be the generator or the assigned transporter of every e-way bill on it, and each must still be active.
Costs 3 credits per successful consolidated e-way bill, however many e-way bills it carries. A rejection is never charged.
| Field | Type | Description |
|---|---|---|
| gstin, gstin_username, password | string | Same as generate — the API username and password set up for the GSTIN you act as |
| ewaybill_nos | array | The 12-digit e-way bill numbers to combine |
| from_place | string | Where the vehicle is now |
| from_state | integer | NIC state code of that place |
| trans_mode | string | 1 Road, 2 Rail, 3 Air, 4 Ship |
| vehicle_no, trans_doc_no, trans_doc_date | string, optional | Vehicle number for road; transport document number and date (dd/MM/yyyy) for rail, air or ship |
E-way bill
Get consolidated e-way bill#
https://www.gstinapi.in/v1/ewaybill/consolidated/detailsThe consolidated e-way bill (trip sheet) and every e-way bill on it, by its 10-digit number.
Costs 1 credit per lookup that actually reaches the government network, same as Get e-way bill details above.
| Field | Type | Description |
|---|---|---|
| gstin, gstin_username, password | string | Same as generate |
| consolidated_ewaybill_no | string | The 10-digit consolidated e-way bill number |
E-way bill
Regenerate consolidated e-way bill#
https://www.gstinapi.in/v1/ewaybill/consolidated/regenerateRe-issues a consolidated e-way bill after a vehicle change. This is not an in-place edit: it returns a new consolidated e-way bill number and the previous one is cancelled. Only the generator or the current transporter can do this, and only active, unexpired e-way bills carry across.
Costs 3 credits per successful regeneration. A rejection is never charged.
| Field | Type | Description |
|---|---|---|
| gstin, gstin_username, password | string | Same as generate |
| consolidated_ewaybill_no | string | The current 10-digit consolidated e-way bill number |
| from_place, from_state | string, integer | Where the vehicle is now, and its NIC state code |
| trans_mode | string | 1 Road, 2 Rail, 3 Air, 4 Ship |
| reason_code | string | One character: 1 Break Down, 2 Transhipment, 3 Others, 4 First Time |
| reason_remark | string | Up to 50 characters |
| vehicle_no, trans_doc_no, trans_doc_date | string, optional | Vehicle number for road; transport document number and date (dd/MM/yyyy) for rail, air or ship |
E-way bill
List bills assigned to a transporter#
https://www.gstinapi.in/v1/ewaybill/transporter/listThe e-way bills assigned to your GSTIN as transporter, by the date they were generated. Optionally narrow to one generator’s GSTIN or state — one filter at a time, not both.
Costs 1 credit per call that actually reaches the government network, including one that returns an empty list.
| Field | Type | Description |
|---|---|---|
| gstin, gstin_username, password | string | Same as generate — the GSTIN acting as transporter |
| date | string | The date the e-way bills were generated, dd/MM/yyyy |
| gen_gstin | string, optional | Only bills generated by this GSTIN |
| state_code | string, optional | Only bills whose generator is in this 2-digit state (active bills only; rate-limited upstream) |
E-way bill
Reject an e-way bill#
https://www.gstinapi.in/v1/ewaybill/rejectRejects an e-way bill another party generated against your GSTIN. Only the recipient can reject a document — never its own generator — and only within the government network's own rejection window, both enforced upstream.
Costs 3 credits per successful rejection, the same price as generate/cancel/close.
| Field | Type | Description |
|---|---|---|
| gstin | string | Same GSTIN as generate |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to reject — must have been generated by someone else, against your GSTIN |
E-way bill
Cancel an e-way bill#
https://www.gstinapi.in/v1/ewaybill/cancelCancels a previously generated e-way bill on the government network.
Costs 3 credits per successful cancellation — the same price as generating one.
| Field | Type | Description |
|---|---|---|
| gstin | string | Your GSTIN, with e-way bill API access set up |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to cancel |
| cancel_reason_code | string, optional | Defaults to the government network's own default reason if omitted |
| cancel_remark | string, optional | Free-text remark |
E-way bill
Close an e-way bill#
https://www.gstinapi.in/v1/ewaybill/closeMarks an e-way bill as closed once the goods movement it covers has actually happened — different from cancelling one that should never have existed.
Costs 3 credits per successful close.
| Field | Type | Description |
|---|---|---|
| gstin | string | Your GSTIN, with e-way bill API access set up |
| gstin_username | string | Same credentials as generate |
| password | string | Same credentials as generate |
| ewaybill_no | string | The e-way bill number to close |
| closure_date | string | dd/MM/yyyy |
| remarks | string, optional | Free-text remark |
Sandbox / test mode#
Add "test_mode": true to any e-way bill endpoint to build and test your integration before spending a credit or even having your e-way bill API access set up yet. Required fields are still validated, but the real government network is never called and nothing is charged, however low your balance is.
TEST- so it can never be mistaken for a real government-issued document.curl -X POST "https://www.gstinapi.in/v1/ewaybill" \
-H "x-api-key: gak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"gstin": "24AAAAA0000A1Z5",
"gstin_username": "your_ewb_api_username",
"password": "your_ewb_api_password",
"payload": { "supplyType": "O", "docType": "INV", "docNo": "INV-1001" },
"test_mode": true
}'{
"success": true,
"test_mode": true,
"ewaybill_no": "TEST-EWB-9F3A2B10",
"ewaybill_date": "13/09/2026",
"valid_upto": "15/09/2026",
"doc_no": "INV-1001",
"distance_km": "10",
"message": "Synthetic response — test_mode, no real e-way bill was generated.",
"billed_to": "test_mode",
"credits_remaining": 25,
"free_remaining": 0
}Test mode proves your integration end-to-end — authentication, request shape, response shape, billing behaviour. It does not simulate what the government network would actually do with a given payload (a malformed field, an inactive GSTIN), since that validation only exists on the real network.
Safe retries after a timeout#
A government-network round trip can occasionally run long enough to hit this API's own timeout before a response comes back — leaving it genuinely unclear whether the e-way bill was filed. The safe recovery is to retry with the exact same document number, never a new one.
/v1/ewaybill— a retry with the samedocNoreturns the already-generated e-way bill as a normal success, withalready_existed: true, billed the same as a fresh one./v1/ewaybill/lookup— if you would rather check than retry, look the document up directly bydoc_typeanddoc_no.
Retrying with a different document number instead is what actually risks a duplicate filing — the government network has no way to know the two calls were meant to be the same attempt.
Integrate using AI — e-way bill
Same idea as the main AI prompt above, scoped to generating and managing e-way bills — including the one rule that matters most here: how to safely retry after a timeout without risking a duplicate government filing.
- Click Copy prompt, then paste it into your AI assistant.
- Replace
MY PROJECTwith what you are building. - Paste your API key and your e-way bill credentials where the assistant tells you to.
I want to add e-way bill generation and management (Indian GST filing) to my
project using the gstinapi.in REST API. Please write the integration code for me.
MY PROJECT: <describe it — e.g. "a Node.js Express app", "a WordPress site",
"a Google Sheet", "a Python script", "a React form">
There is no official client library for this yet (the gstinapi Node.js/Python
packages only cover GSTIN lookup) — call the REST endpoints directly with
whatever HTTP client my language/framework normally uses.
API DETAILS
Base URL : https://www.gstinapi.in
Auth : header x-api-key: <MY_API_KEY>
Rate limit : 60 requests per minute per key
ENDPOINTS
POST /v1/ewaybill generate an e-way bill
POST /v1/ewaybill/lookup find one already generated, by doc_type + doc_no
POST /v1/ewaybill/details full details of one, by e-way bill number
POST /v1/ewaybill/consolidated combine several into one consolidated e-way bill (trip sheet)
POST /v1/ewaybill/consolidated/details read a consolidated e-way bill, by its 10-digit number
POST /v1/ewaybill/consolidated/regenerate re-issue one after a vehicle change (returns a NEW number)
POST /v1/ewaybill/transporter/list e-way bills assigned to my GSTIN as transporter, by generated date
POST /v1/ewaybill/other-party/list e-way bills other parties raised against my GSTIN, by generated date
POST /v1/ewaybill/update-vehicle update the vehicle / Part-B
POST /v1/ewaybill/update-transporter assign or change the transporter
POST /v1/ewaybill/extend-validity extend the validity of one about to expire
POST /v1/ewaybill/reject reject one raised against my GSTIN
POST /v1/ewaybill/cancel cancel one
POST /v1/ewaybill/close close one (goods movement completed)
EXAMPLE REQUEST — generate an e-way bill, in sandbox (test_mode)
curl -X POST "https://www.gstinapi.in/v1/ewaybill" \
-H "x-api-key: MY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"gstin": "24AAAAA0000A1Z5",
"gstin_username": "ewb_api_username",
"password": "ewb_api_password",
"payload": { "supplyType": "O", "docType": "INV", "docNo": "INV-1001" },
"test_mode": true
}'
The other endpoints take the same gstin / gstin_username / password plus
ewaybill_no, and their own fields (vehicle_no, transporter_id,
remaining_distance, ...). Send "test_mode": true on any of them to try it free.
SUCCESS RESPONSE (HTTP 200) — generate
{
"success": true,
"ewaybill_no": "351000000123",
"ewaybill_date": "14/09/2026",
"valid_upto": "16/09/2026",
"doc_no": "INV-1001",
"already_existed": false,
"billed_to": "paid",
"credits_remaining": 197
}
ERROR RESPONSE — a genuine rejection from the filing system (bad payload,
expired registration) — never charged
{ "success": false, "error": "human readable reason", "error_code": "upstream code, or null" }
STATUS CODES
200 success — real filing, or a synthetic test_mode response
400 missing required fields, or the filing system rejected the document — never charged
401 missing or invalid x-api-key
402 insufficient credits for this call (costs more than 1 credit)
403 account deactivated
429 rate limit exceeded
502 filing system temporarily unavailable — safe to retry, never charged
REQUIREMENTS
1. Read the gstinapi.in API key AND the gstin_username/password from environment
variables. Never hardcode any of them, and never expose them in
browser/client-side code — call this API from my server only.
2. Build and test everything with "test_mode": true first. It skips the
credit check and the real government network entirely, returns a
synthetic response with every reference number TEST-prefixed, and needs
no real e-way bill registration — only remove it once ready to
file for real.
3. Handle every status code above with a clear message. Treat a 400
rejection (bad data, never charged) differently from a 502 (transient,
safe to retry) differently from a 402 (buy more credits) — do not lump
them into one generic "failed" state.
4. On 429 and 502 only, retry with exponential backoff, maximum 3 attempts.
Never retry a 400/401/402/403.
5. The single most important rule: if a request times out and you cannot
tell whether the document was actually filed, retry with the EXACT SAME
docNo — never a new one. The API detects this and returns the real
original document with already_existed: true, billed the same as a fresh
success. Retrying with a different number is what actually risks a
duplicate government filing.
6. Parse every response leniently. New fields are added without notice;
existing fields are never removed or renamed without a new API version.
Ignore unknown keys rather than validating with a strict/closed schema.
7. My GSTIN needs e-way bill API access set up once, separately from this
API key, before any non-test_mode call will work — remind me to contact
gstinapi.in support if a real (non-test_mode) call fails because of this.
8. Log credits_remaining after each real call so I can watch my balance.
Tell me exactly which file to create, where to put my credentials, and how to run it.Your API key and your e-way bill credentials are passwords — keep them on your server. Anyone who has them can spend your credits or file on your behalf.
E-invoice
Generate and cancel e-invoices (IRN)
Register an invoice with the government e-invoice system and get back its IRN, acknowledgement and signed QR code. Include the transport details in the same call to get the e-way bill too, for one charge. gstinapi.in owns the credit charge; the filing itself happens on that network.
E-invoice
Generate an e-invoice#
https://www.gstinapi.in/v1/einvoiceRegisters the invoice with the government e-invoice system and returns its IRN, acknowledgement and signed QR code. Put EwbDtls in the payload to ask for the e-way bill in the same call; ewaybill_status in the response says what happened to it.
IRN issued, e-way bill refused (ewaybill_status: "failed")
The government system checks the IRN and the e-way bill separately. It can issue the IRN and refuse only the e-way bill, for example over a distance, vehicle number or transporter field. The response is then success: true with the real IRN, ewaybill_no: null, and the reason in ewaybill_error.
- Save the IRN, ack number and QR code: the e-invoice is valid and was charged.
- Fix what ewaybill_error names.
- Generate the e-way bill with POST /v1/ewaybill for the same document number and date. It is charged as a separate e-way bill.
Do not resend the invoice to /v1/einvoice for this: the IRN already exists, so you get the same IRN back (already_existed: true) and the e-way bill is not tried again. Test this branch in your code with test_mode and "test_scenario": "ewaybill_failed".
Costs 3 credits per successful e-invoice — not per attempt.
| Field | Type | Description |
|---|---|---|
| gstin | string | Your GSTIN, with e-invoice API access set up |
| einv_username | string | API username from that registration |
| password | string | API password from that registration |
| payload | object | One NIC e-invoice (INV-01) JSON payload |
| txn_type | string, optional | INV, CDN or DBN — defaults to INV |
| test_scenario | string, optional | With test_mode only. "ewaybill_failed" returns the IRN-issued, e-way-bill-refused response, so you can build for it. |
EwbDtls: the e-way bill part of the payload
| Field | Description |
|---|---|
| Distance | Kilometres. 0 lets the government system work it out from the two pincodes; it returns the figure as ewaybill_distance_km. |
| TransMode | 1 road, 2 rail, 3 air, 4 ship |
| VehNo, VehType | Vehicle number (e.g. GJ05AB1234) and R regular or O over-dimensional, for road |
| TransId, TransName | Transporter GSTIN/ID and name, when a transporter moves the goods |
| TransDocNo, TransDocDt | Transport document (LR, RR, airway bill) number and date, when there is one |
Leave a field out entirely when you do not use it. The government schema rejects empty strings, which is a common reason the IRN is issued but the e-way bill is refused.
Response fields
| Field | Description |
|---|---|
| irn | The 64-character Invoice Reference Number |
| ack_no, ack_date | Acknowledgement number and time from the e-invoice system |
| doc_no | Your invoice number, as the signed invoice records it |
| qr_code_image, signed_invoice | Signed QR payload to print, and the full signed invoice (see the note below) |
| ewaybill_status | "generated", "failed" (IRN issued, e-way bill refused) or "not_requested" (no EwbDtls sent) |
| ewaybill_no, ewaybill_date, ewaybill_valid_upto | The e-way bill, when ewaybill_status is "generated"; otherwise null |
| ewaybill_error | When ewaybill_status is "failed": code and message from the government system (code can be null if it gave none), and details listing every reason. Otherwise null. |
| ewaybill_distance_km | Only when you sent Distance 0 and the system worked it out from the pincodes |
| fix | Only when ewaybill_status is "failed": what to do next |
| notices | Only when the e-invoice system sent extra advisories with the IRN: a list of code and message |
| already_existed | true when this invoice number already had an IRN and the existing one was returned (see safe retries) |
| billed_to, credits_remaining, free_remaining | What was charged and what is left |
qr_code_image and signed_invoice are the filing system’s SignedQRCode and SignedInvoice respectively — passed through as-is, not re-rendered. qr_code_image is just the QR payload meant for printing on the invoice (you’ll need a QR-rendering library to turn it into a scannable image); signed_invoice is the full tamper-evident signed invoice record, for audit and reconciliation.
E-invoice
Check an e-invoice payload (free)#
https://www.gstinapi.in/v1/einvoice/validateFree and offline, like the e-way bill check: no portal credentials, no credits, no call to the government network. It checks the INV-01 rules that most often cause a refusal: invoice number and date, seller and buyer GSTIN and state codes, pincodes, HSN, GST slabs, every line and document total, IGST versus CGST and SGST for the place of supply, and any embedded e-way bill details.
Errors are unambiguous rules; warnings are probable mistakes. A payload that passes can still be refused for reasons only the government network can see, and a refused live call returns a likely_causes list from this same check. The check never blocks a live call.
E-invoice
Cancel an e-invoice#
https://www.gstinapi.in/v1/einvoice/cancelCancels a previously generated e-invoice by its IRN.
Costs 3 credits per successful cancellation.
| Field | Type | Description |
|---|---|---|
| gstin | string | Your GSTIN, with e-invoice API access set up |
| einv_username | string | Same e-invoice API user as generate. Older integrations can keep sending it as gstin_username; if both are sent, einv_username is used. |
| password | string | Same credentials as generate |
| irn | string | The invoice reference number to cancel |
| cancel_reason | string, optional | NIC's own reason code |
| cancel_remark | string, optional | Free-text remark |
Sandbox / test mode#
Add "test_mode": true to either e-invoice endpoint to build and test your integration before spending a credit or even having your e-invoice API access set up yet. Required fields are still validated, but the real government network is never called and nothing is charged, however low your balance is.
TEST- so it can never be mistaken for a real government-issued document.curl -X POST "https://www.gstinapi.in/v1/einvoice" \
-H "x-api-key: gak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"gstin": "24AAAAA0000A1Z5",
"einv_username": "your_einvoice_portal_username",
"password": "your_einvoice_portal_password",
"payload": { "docDtls": { "no": "INV-1001" } },
"test_mode": true
}'{
"success": true,
"test_mode": true,
"irn": "TEST-IRN-9F3A2B10",
"ack_no": "TEST-ACK-1C4D5E20",
"ack_date": "13/09/2026 12:00:00",
"doc_no": "INV-1001",
"qr_code_image": "iVBORw0KG...",
"signed_invoice": "TEST-HEADER.TEST-PAYLOAD.TEST-SIGNATURE",
"ewaybill_status": "not_requested",
"ewaybill_no": null,
"ewaybill_date": null,
"ewaybill_valid_upto": null,
"ewaybill_error": null,
"already_existed": false,
"billed_to": "test_mode",
"credits_remaining": 25,
"free_remaining": 0
}Add EwbDtls to the payload and the test response carries a TEST- e-way bill with ewaybill_status "generated". Add "test_scenario": "ewaybill_failed" as well to get the response for an IRN that was issued while the e-way bill was refused, with a sample ewaybill_error and fix, so that branch of your code is tested before it happens live.
Test mode proves your integration end-to-end — authentication, request shape, response shape, billing behaviour. It does not simulate what the government network would actually do with a given payload (a malformed field, an inactive GSTIN), since that validation only exists on the real network.
Safe retries after a timeout#
A government-network round trip can occasionally run long enough to hit this API's own timeout before a response comes back — leaving it genuinely unclear whether the invoice was registered. The safe recovery is to retry with the exact same invoice number, never a new one.
/v1/einvoice— a retry with the same invoice number returns the already-generated invoice's realirn/ack_no/qr_code_image/signed_invoicewithalready_existed: trueset, billed the same as a fresh success — never a duplicate filing, and never a lost IRN.
Retrying with a different invoice number instead is what actually risks a duplicate filing — the government network has no way to know the two calls were meant to be the same attempt.
Integrate using AI — e-invoice
Same idea as the main AI prompt above, scoped to generating and cancelling e-invoices — including how to safely retry after a timeout without registering the same invoice twice.
- Click Copy prompt, then paste it into your AI assistant.
- Replace
MY PROJECTwith what you are building. - Paste your API key and your e-invoice credentials where the assistant tells you to.
I want to add e-invoice (IRN) generation (Indian GST filing) to my
project using the gstinapi.in REST API. Please write the integration code for me.
MY PROJECT: <describe it — e.g. "a Node.js Express app", "a WordPress site",
"a Google Sheet", "a Python script", "a React form">
There is no official client library for this yet (the gstinapi Node.js/Python
packages only cover GSTIN lookup) — call the REST endpoints directly with
whatever HTTP client my language/framework normally uses.
API DETAILS
Base URL : https://www.gstinapi.in
Auth : header x-api-key: <MY_API_KEY>
Rate limit : 60 requests per minute per key
ENDPOINTS
POST /v1/einvoice generate an e-invoice (IRN) — include EwbDtls in the
payload to also get an e-way bill in the same call
POST /v1/einvoice/cancel cancel one, by irn
EXAMPLE REQUEST — generate an e-invoice, in sandbox (test_mode)
curl -X POST "https://www.gstinapi.in/v1/einvoice" \
-H "x-api-key: MY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"gstin": "24AAAAA0000A1Z5",
"einv_username": "einv_api_username",
"password": "einv_api_password",
"payload": { "DocDtls": { "Typ": "INV", "No": "INV-1001", "Dt": "14/09/2026" } },
"test_mode": true
}'
Cancel takes gstin / gstin_username / password plus the irn to cancel.
SUCCESS RESPONSE (HTTP 200) — generate
{
"success": true,
"irn": "35a1b2c3...",
"ack_no": "112010012345678",
"ack_date": "14/09/2026 10:00:00",
"qr_code_image": "<base64 — the filing system's SignedQRCode, not a rendered image>",
"signed_invoice": "<JWS string — the filing system's SignedInvoice, the full signed legal record>",
"ewaybill_no": null,
"already_existed": false,
"billed_to": "paid",
"credits_remaining": 197
}
ERROR RESPONSE — a genuine rejection from the filing system (bad payload,
expired registration) — never charged
{ "success": false, "error": "human readable reason", "error_code": "upstream code, or null" }
STATUS CODES
200 success — real filing, or a synthetic test_mode response
400 missing required fields, or the filing system rejected the document — never charged
401 missing or invalid x-api-key
402 insufficient credits for this call (costs more than 1 credit)
403 account deactivated
429 rate limit exceeded
502 filing system temporarily unavailable — safe to retry, never charged
REQUIREMENTS
1. Read the gstinapi.in API key AND the einv_username/password (gstin_username/password for cancel) from environment
variables. Never hardcode any of them, and never expose them in
browser/client-side code — call this API from my server only.
2. Build and test everything with "test_mode": true first. It skips the
credit check and the real government network entirely, returns a
synthetic response with every reference number TEST-prefixed, and needs
no real e-invoice registration — only remove it once ready to
file for real.
3. Handle every status code above with a clear message. Treat a 400
rejection (bad data, never charged) differently from a 502 (transient,
safe to retry) differently from a 402 (buy more credits) — do not lump
them into one generic "failed" state.
4. On 429 and 502 only, retry with exponential backoff, maximum 3 attempts.
Never retry a 400/401/402/403.
5. The single most important rule: if a request times out and you cannot
tell whether the document was actually filed, retry with the EXACT SAME
invoice number — never a new one. The API detects this and returns the real
original document with already_existed: true, billed the same as a fresh
success. Retrying with a different number is what actually risks a
duplicate government filing.
6. qr_code_image and signed_invoice are passed through exactly as the filing
system returned them — qr_code_image needs a QR-rendering library on my
side to become a scannable image; signed_invoice is the full signed
record, for audit/reconciliation, not something to parse further.
7. If EwbDtls (vehicle number, transport mode, distance) is in the payload,
the e-way bill is generated in the same call and billed once, not twice.
Omit the transporter id/name/document fields entirely when unused — the
schema rejects empty strings for them.
8. Parse every response leniently. New fields are added without notice;
existing fields are never removed or renamed without a new API version.
Ignore unknown keys rather than validating with a strict/closed schema.
9. My GSTIN needs e-invoice API access set up once, separately from this
API key, before any non-test_mode call will work — remind me to contact
gstinapi.in support if a real (non-test_mode) call fails because of this.
10. Log credits_remaining after each real call so I can watch my balance.
Tell me exactly which file to create, where to put my credentials, and how to run it.Your API key and your e-invoice credentials are passwords — keep them on your server. Anyone who has them can spend your credits or file on your behalf.
GST return filing: how it works#
Filing a GST return is several calls, because a taxpayer reviews a return before it is final and the last steps cannot be undone. The same pattern applies to GSTR-1 and GSTR-3B: save the return and read the summary, review it, then file with a one-time password.
- Connect the GSTIN once. In the dashboard under GST Return Filing, or from your own server with the connect calls below, enter the GST portal username. The GST portal sends a one-time password to the registered mobile or email; confirm it. The connection is kept alive automatically within the API-access duration chosen on the GST portal (6 hours to 30 days) and covers any number of filings. Before that, the GSTIN needs API access set up on the GST portal under My Profile, Manage API Access; since 2026 that includes selecting a GSP-ASP combination on the same page, and the current steps with what to select are in the GST portal API access guide.
- Save and preview. Send the return JSON to
/save. Nothing is filed or paid. You get the summary and achksumthat identifies exactly what was saved. - GSTR-3B only: pay the liability with
/offset. This pays tax from the cash and credit ledgers and cannot be undone. - Request the filing OTP. GSTR-1 asks for it as part of
/save; GSTR-3B has its own/evc-otp. It goes to the mobile of the authorised signatory, and is different from the connection OTP. - File with
/file, passing the OTP and the checksum you reviewed. If the saved return has changed since you reviewed it, GSTR-3B refuses with 409CHECKSUM_CHANGEDrather than filing something different.
Billing. Saving, previewing, the OTP request and paying the liability are free. A filing is charged only when it succeeds; a failed filing never is. Live filing needs an account that has bought a credit pack.
Try it first. Add "test_mode": true to any call below. It returns a synthetic response in the live shape, never reaches the GST network, files and pays nothing, and costs nothing, so you can build the whole flow before the first real filing.
New and supervised. Return filing is new. Your first real filings should be watched by someone, on a return you can check on the GST portal. Filing by DSC and GSTR-9 are not available.
GST return filing
Connect a GSTIN: request the OTP#
https://www.gstinapi.in/v1/gst/filing/connectionsThe same connection the dashboard makes, from your own server. Send the GSTIN and the GST portal username that GSTIN logs in with: not a practitioner login, and not the username of another GSTIN under the same PAN. Before the first connection the GSTIN needs API access set up on the GST portal, including the GSP-ASP combination; the GST portal API access guide has the steps and what to select. There is no test_mode here: this call sends a real OTP.
These four calls take x-api-key like every other endpoint (or the dashboard session token). None of them is charged. One connection covers GSTR-1, GSTR-3B, GSTR-2A, GSTR-2B and IMS for that GSTIN, from any IP.
GST return filing
Connect a GSTIN: verify the OTP#
https://www.gstinapi.in/v1/gst/filing/connections/{gstin}/verifySend the 6-digit OTP within 10 minutes of requesting it. A successful verify proves the username and the OTP; whether the GSTIN belongs to that username is checked by the GST network on the first data call, so a connection can show active and still answer RET11402 "API Authorization Failed" on a fetch. That response carries the remedy as fix and is not charged.
GST return filing
Connections: list#
https://www.gstinapi.in/v1/gst/filing/connectionsEvery GSTIN connected on this account with its live status. Poll this instead of guessing whether a session is still valid.
GST return filing
Connections: disconnect#
https://www.gstinapi.in/v1/gst/filing/connections/{gstin}Use it before reconnecting with a different username, or when a GSTIN should no longer be reachable from this account.
GST return filing
GSTR-1: save and preview#
https://www.gstinapi.in/v1/gstr1/saveret_period is MMYYYY. payload is the GSTR-1 JSON in the GST network's own schema (gstin, fp, b2b and so on), passed through untouched. pan is the PAN of the authorised signatory who will receive the OTP.
GST return filing
GSTR-1: file#
https://www.gstinapi.in/v1/gstr1/filesummary is the object /v1/gstr1/save returned, unchanged: its checksum is what the GST network signs off on. evc_otp is the six-digit OTP sent for this filing.
GST return filing
GSTR-3B: save and preview#
https://www.gstinapi.in/v1/gstr3b/savepayload is the GSTR-3B JSON in the GST network's own schema (sup_details, inter_sup, itc_elg, intr_ltfee and so on), passed through untouched.
GST return filing
GSTR-3B: pay liability#
https://www.gstinapi.in/v1/gstr3b/offsetpayload carries the ledger split in the GST network's schema: pdcash for what is paid in cash and pditc for what is paid from input credit.
GST return filing
GSTR-3B: request the OTP#
https://www.gstinapi.in/v1/gstr3b/evc-otpAsk for the OTP just before you file; it is valid for a short time.
GST return filing
GSTR-3B: file#
https://www.gstinapi.in/v1/gstr3b/filechksum is the checksum from /save (or from /offset, if you paid first). The return is read again before filing, and a mismatch stops the filing.
GSTR-2B: how it works#
GSTR-2B is the GST network's own auto-drafted statement of input tax credit available for a return period, built from what your suppliers filed. It is read-only — nothing is filed or paid — so unlike GSTR-1/3B above, there is no final step waiting on anything to be switched on.
- Connect the GSTIN once. Same one-time OTP connection as GSTR-1/3B, in the dashboard under GSTR-2B & GST Return Filing. One connection covers both, and the GST portal side is the same: Manage API Access with the GSP-ASP combination selected, see the guide.
- Generate it, if needed. GSTN normally generates 2B itself once a month;
/generateasks for it on demand and returns anint_tran_idto poll. - Poll with
/generate/statusuntil generation is done, if you called /generate. - Fetch it with
/fetch. This is the only billed call in this flow — charged only on a real success.
Try it first. Add "test_mode": true to any call below for a synthetic response that never reaches the GST network and costs nothing.
New. This is the first release of GSTR-2B on this API. The exact shape of the statement under data can still move as we see more real periods; read it defensively rather than assuming every field shown below is final.
GST return filing
GSTR-2B: generate#
https://www.gstinapi.in/v1/gstr2b/generateret_period is MMYYYY. You only need this if a 2B does not already exist for the period, or you want it regenerated after suppliers filed late.
GST return filing
GSTR-2B: generation status#
https://www.gstinapi.in/v1/gstr2b/generate/statusint_tran_id is the one /generate returned.
GST return filing
GSTR-2B: fetch statement#
https://www.gstinapi.in/v1/gstr2b/fetchret_period is MMYYYY. file_num (optional) identifies which generation of that period's 2B to read, if it was regenerated more than once; leave it out for the default.
GSTR-2A: how it works#
GSTR-2A is the GST network's live, continuously-updated purchase-side statement, built as your suppliers file — unlike GSTR-2B's frozen monthly snapshot above. It is read-only, same as GSTR-2B, and uses the same one-time OTP-connected GSTIN as GSTR-1/3B/2B.
The GSTIN needs API access set up on the GST portal first, under My Profile, Manage API Access, including the GSP-ASP combination the portal asks for since 2026 (steps and what to select: GST portal API access guide). A fetch that answers RET11402 "API Authorization Failed" right after connecting has two known causes: the GSP-ASP combination not selected on the portal, or a GST portal username that belongs to another GSTIN; the error response carries the remedy under fix, and the call is not charged.
Unlike GSTR-2B's one consolidated statement, GSTR-2A is split into 12 separate tables. One call fetches one table for one GSTIN/period; getting the whole 2A for a period means up to 12 calls.
b2b— B2B invoicesb2ba— Amended B2Bcdn— Credit/debit notescdna— Amended credit/debit notesimpg— Import of goodsimpgsez— Import from SEZtcs— Tax collected at sourcetds— Tax deducted at sourceamdhist— Import bill-of-entry amendment historyecom— E-commerceecoma— Amended e-commerceisd— Input Service Distributor credits
Try it first. Add "test_mode": true to the call below for a synthetic response that never reaches the GST network and costs nothing.
Empty is normal. A GSTIN with no entries for a table in that period gets a 400 with error_code RET13509, "No document found for the provided Inputs". It is not charged. Treat it as an empty table rather than a failed call — most accounts will see this on several tables every period.
GST return filing
GSTR-2A: fetch a table#
https://www.gstinapi.in/v1/gstr2a/fetchret_period is MMYYYY. table picks which of the 12 tables above to fetch — one flat price per call, regardless of table. ctin and fromtime are optional filters accepted by most tables; amdhist additionally requires portcode, benumber and bedate.
Reference
Credits, errors and limits
The rules shared by every endpoint on this page.
Credits#
- Every account gets up to 75 free credits — 10 on signup and 65 more across the setup steps on the dashboard — no card required, and they never expire. Purchased credits are only used once the free ones run out.
- A successful lookup costs 1 credit. Your balance is returned as
credits_remainingon every call. - A malformed GSTIN (400) is free — validate client-side and you never waste a credit.
- Credits never expire.
HTTP Status Codes#
| Code | Status | Meaning |
|---|---|---|
| 200 | OK | Success — GSTIN found and returned. 1 credit charged. |
| 400 | Bad Request | Invalid GSTIN format — no credit charged |
| 401 | Unauthorized | Missing or invalid x-api-key header |
| 402 | Payment Required | Insufficient credits — recharge your account |
| 403 | Forbidden | Account deactivated — contact support |
| 404 | Not Found | GSTIN not registered in GST database |
| 429 | Too Many Requests | Rate limit exceeded (60 req/min) |
| 502 | Bad Gateway | GST provider temporarily unavailable — retry |
⚡ Rate Limits#
Default limit is 60 requests per minute per API key. A brand-new account is held to 10 per minute until it has verified its email or holds purchased credits — the cap lifts on its own. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers; on a 429, wait for the reset rather than opening a second key. Need more? Contact support.
Free integration: sample mode & Postman#
Build and test your whole integration before you pay anything: no setup fee, no annual fee, no minimum. Every GSTIN and PAN endpoint answers a test input with a fixed sample in the live response shape, never consumes a credit and never calls the GST network. When your code works, top up and switch the input to a real GSTIN or PAN; nothing else changes.
| Endpoint | Test input |
|---|---|
| GET /v1/gstin/{gstin} | 00AAAAA0000A1ZT |
| GET /v1/gstin/{gstin}/returns?fy= | 00AAAAA0000A1ZT |
| GET /v1/gstin/{gstin}/compliance?fy= | 00AAAAA0000A1ZT |
| GET /v1/gstin/{gstin}/filing-preference?fy= | 00AAAAA0000A1ZT |
| GET /v1/gstin/{gstin}/complete | 00AAAAA0000A1ZT |
| GET /v1/pan/{pan}/gstins, /verify | AAAAA0000A |
| POST /v1/ewaybill, /v1/einvoice | "test_mode": true |
State code 00 does not exist on the GST network, so the test GSTIN can never collide with a live registration, and the test PAN is the one inside it. A sample call authenticates like any call but skips the balance gate, so it works on an empty wallet (including an empty PAN search wallet) and CI keeps passing:
curl "https://gstinapi.in/v1/gstin/00AAAAA0000A1ZT/returns?fy=2025-26" -H "x-api-key: YOUR_API_KEY"
The response carries "test": true and "billed_to": "test" so it can never be mistaken for live data.
To explore the API in Postman or Insomnia, import the OpenAPI spec straight from /openapi.json (Postman: Import → Link). Every endpoint, parameter and response schema is in there.
Resources & downloads#
Specs, collections, libraries and guides — everything you can import or install.