If you generate e-way bills or e-invoices from code, you will meet the same handful of error codes again and again. The messages are terse, the codes sometimes arrive with a stray comma ("301,"), and a couple of them mean the opposite of what a retry loop assumes.
This is the list I wish I had when I started. Every code below is one I actually hit against the live network, not one I copied from a PDF.
The ones you will see first
108: Invalid login credentials
The API username or password was refused. It is almost never a typo. The common causes:
- The API user was created for a different GSP (for example inside your accounting software). An API user only works with the GSP it was created for.
- The password has special characters. Letters and digits only is the safe choice.
- You sent your normal portal login instead of the API user.
Fix: create a fresh API user for the GSP you are calling through (e-way bill portal: Registration, then For GSP). The e-invoice portal has its own separate API user, even for the same GSTIN.
604: Already generated
You sent a document that already has an e-way bill. This is the one that bites retry loops. If your first call timed out, you do not know whether the e-way bill was filed. Retrying with the same document number cannot create a duplicate: the network answers 604, and the existing e-way bill can be fetched by document type and number. Generating with a new document number is what creates a duplicate.
The clean pattern: on a timeout, retry the same payload, and treat 604 as "go and fetch the existing one", not as a failure. (The API used in the example below does that recovery for you and returns the real e-way bill with already_existed: true; the branch in the handler is for when you call the network directly.)
728: You can cancel within 24 hours
Cancellation is only possible within 24 hours, by the generator. After that there is no API to undo it; you let it expire and issue a correct one. If you generate e-way bills while testing, cancel them in the same session. They cannot be cancelled tomorrow.
The "wrong thing, right shape" ones
| Code | Message (roughly) | What it usually means |
|---|---|---|
| 301 | Invalid eway bill number | The 12 digits do not exist, or are not visible to your GSTIN |
| 325 | Could not retrieve data | Valid number, but not yours to read |
| 418 | No record found | A lookup by document number found nothing. The document type must match what was filed: a challan (CHL) is not found when you ask for INV |
| 336 | Could not retrieve transporter data | Nothing is assigned to you as transporter on that date. Treat it as an empty list, not an error |
| 341 | Generated by you, cannot reject | Rejection is for e-way bills others raised against you. Cancel your own instead |
| 381 | No Part-B / vehicle entry | You tried to extend validity before any vehicle was entered. Update the vehicle first |
| 387 | No record for consolidated e-way bill | The trip sheet number is wrong or not yours |
| 658 | Rate control limit exceeded | The by-state transporter list allows about 50 calls per 15 minutes |
Two of these are worth a special mention.
336 is not an error. The network answers an empty transporter list with an error code. If you surface it to a user as a failure, you will get a support ticket for every quiet day.
381 is an ordering bug in your code. Extend validity needs a vehicle on the e-way bill. Add the vehicle (Part B) first.
Two reason-code tables you should never share
Cancelling asks for a reason code, and the e-way bill and e-invoice portals use different numbers for the same words:
| Code | E-way bill cancel | E-invoice (IRN) cancel |
|---|---|---|
| 1 | Duplicate | Duplicate |
| 2 | Order Cancelled | Data entry mistake |
| 3 | Data Entry mistake | Order Cancelled |
| 4 | Others | Others |
If you reuse one lookup table for both, half your cancellations carry the wrong reason and nothing will tell you.
Updating the vehicle uses a third table: 1 Break Down, 2 Transhipment, 3 Others, 4 First Time.
Handling them in code
Normalise the code first (strip the stray comma and anything that is not a digit), then branch on it. A small handler:
const codeOf = e => String(e.error_code || '').replace(/[^0-9]/g, '');
async function call(path, body) {
const res = await fetch(`https://www.gstinapi.in${path}`, {
method: 'POST',
headers: { 'x-api-key': process.env.API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
if (data.success) return data;
switch (codeOf(data)) {
case '108': throw new Error('API credentials refused: recreate the API user for this GSP');
case '604': return lookupExisting(body); // already generated: fetch it
case '336': return { success: true, ewaybills: [] }; // empty list is normal
case '728': throw new Error('Past the 24-hour cancel window: let it expire');
default: throw Object.assign(new Error(data.error), { code: codeOf(data) });
}
}
Two habits that save real money and time:
-
Test without filing anything. Every call above accepts
"test_mode": trueand returns a sample in the live response shape, so you can build the error branches before you have a real GSTIN set up. - Check the payload before you send it. A local validator catches the mechanical mistakes (an HSN code that is not 4, 6 or 8 digits, tax amounts that do not match the taxable value at the stated rate) without a network call. Passing it is not approval: a cancelled GSTIN or a duplicate document is only caught by the network.
A reference page
I kept the full list, with the call each code comes from, in one place: E-way bill and e-invoice API error codes. If you hit a code that is not there, tell me and I will add it.
If you are starting from zero, the credentials setup guide covers the one-time portal steps, which is where most 108s come from.
Top comments (0)