Data AnalyticsPractical workplace data tasks

Check GST invoice number series for gaps, repeats and bad formats

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 27 September 2026 · 6 min read
In this article (7 sections)

Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).

The short answer

To audit a GST invoice series, check four things per invoice number: it is at most 16 characters, it uses only letters, digits, hyphen or dash and slash, it is unique within the financial year, and its numeric part runs without unexplained gaps within its series. The first three come directly from Rule 46(b) of the CGST Rules, 2017. The fourth is an internal control: every missing number should be matched to a cancellation record.

What the rule says

Rule 46 of the CGST Rules, 2017 lists the particulars a tax invoice must contain. Clause (b) requires a consecutive serial number not exceeding sixteen characters, in one or multiple series, containing letters, numerals or the special characters hyphen or dash and slash, and unique for a financial year. That gives three machine-checkable conditions. Treat the rule text as the reference and your tax adviser as the authority on how it applies to your business; the check below only tests the register against the wording.

Typical ways a series breaks

Invoice numbers come from billing software, from a manual book at a small branch, and from an Excel template someone built years ago. Common failures:

  • a new format introduced mid-year that includes the full year and a document type, pushing the number past 16 characters;
  • spaces instead of slashes, typed by hand;
  • the series not reset on 1 April, so an April invoice still carries last year's code;
  • the same number saved twice when a bill was edited and re-saved;
  • numbers skipped when a draft was discarded, with no record of why.

The check

The register below is synthetic: two branch series, Noida (NOI) and Ghaziabad (GZB), across the change of financial year on 1 April 2026. The cancellation register records one cancelled number.

python
import re
import pandas as pd

# Synthetic sales register: two branch series, crossing the 1 April 2026 year boundary.
reg = pd.DataFrame({
    "inv_no": ["NOI/25-26/0141", "NOI/25-26/0142", "NOI/25-26/0144", "NOI/25-26/0145",
               "NOI/26-27/0001", "NOI/26-27/0002", "NOI/26-27/0002",
               "GZB/25-26/0088", "GZB/25-26/0089", "GZB/26-27/0001", "GZB/26-27/0003",
               "GZB/2026-27/INV/0004", "NOI 26-27 0003", "NOI/25-26/0146"],
    "inv_date": pd.to_datetime(["2026-03-28", "2026-03-30", "2026-03-31", "2026-03-31",
                                "2026-04-01", "2026-04-02", "2026-04-02",
                                "2026-03-30", "2026-03-31", "2026-04-01", "2026-04-03",
                                "2026-04-04", "2026-04-04", "2026-04-01"]),
    "taxable": [42000, 18500, 7600, 12999, 31000, 9800, 9800, 56000, 4400, 22000, 15000,
                8100, 6000, 11200],
})
cancelled = {"NOI/25-26/0143"}             # from the cancellation register, with reasons kept there

# 1. Format: at most 16 characters; only letters, digits, hyphen/dash and slash.
ok_chars = reg["inv_no"].str.fullmatch(r"[A-Za-z0-9/\-]+")
reg["format_issue"] = ""
reg.loc[reg["inv_no"].str.len() > 16, "format_issue"] = "longer than 16 characters"
reg.loc[~ok_chars, "format_issue"] = "character outside letters, digits, - and /"

# 2. Financial year of the invoice date (April to March), and the year the number claims.
fy_start = reg["inv_date"].dt.year - (reg["inv_date"].dt.month < 4)
reg["fy"] = fy_start.astype(str) + "-" + ((fy_start + 1) % 100).astype(str).str.zfill(2)
parts = reg["inv_no"].str.extract(r"^(?P<series>[A-Z]+)/(?P<fy_code>\d{2}-\d{2})/(?P<seq>\d+)$")
reg = reg.join(parts)
reg["seq"] = pd.to_numeric(reg["seq"])
mismatch = reg["fy_code"].notna() & (reg["fy_code"] != reg["fy"].str[2:])
reg.loc[mismatch, "format_issue"] = "FY in number does not match invoice date"

print(reg.loc[reg["format_issue"] != "", ["inv_no", "inv_date", "format_issue"]].to_string(index=False))

# 3. Uniqueness within the financial year.
dups = reg[reg.duplicated(["inv_no", "fy"], keep=False)]
print("\nrepeated within FY:\n", dups[["inv_no", "inv_date", "taxable"]].to_string(index=False))

# 4. Gaps per series and year, net of documented cancellations.
good = reg.dropna(subset=["seq"]).drop_duplicates(["inv_no", "fy"])
gaps = []
for (series, fy_code), g in good.groupby(["series", "fy_code"]):
    seen = set(g["seq"].astype(int))
    for n in range(min(seen), max(seen) + 1):
        num = f"{series}/{fy_code}/{n:04d}"
        if n not in seen and num not in cancelled:
            gaps.append(num)
print("\nunexplained gaps:", gaps)
assert gaps == ["GZB/26-27/0002"] and len(dups) == 2

Reading the findings

FindingInvoiceLikely causeAction
Too longGZB/2026-27/INV/0004New template with a four-digit year and a type codeRevert to the approved format before more invoices are issued
Bad characterNOI 26-27 0003Manual entry with spacesCorrect the register entry against the printed invoice
Year mismatchNOI/25-26/0146 dated 1 April 2026Series not reset at the year changeReview with accounts; do not renumber issued invoices on your own
RepeatedNOI/26-27/0002, twiceSame bill saved twiceConfirm whether one row is a duplicate entry or two real invoices
GapGZB/26-27/0002Missing from both register and cancellationsFind the document or record the cancellation

NOI/25-26/0143 is missing too, but it is in the cancellation register, so it is not reported. That is the difference between a gap check that produces noise and one that gets used: explained gaps drop out, and only the ones needing attention remain.

The financial year is computed from the invoice date, April to March, and compared with the year code inside the number. A 25-26 code on 1 April 2026 is exactly the reset failure. Because the rule makes uniqueness a per-year condition, the duplicate check also groups by financial year, so NOI/25-26/0001 and NOI/26-27/0001 do not count as a repeat.

Why the check should not fix anything

It is tempting to renumber the long or badly formatted invoices in the register so that the report looks clean. Don't. The invoice already issued to the customer carries the number printed on it, and the register must match that document. The check's job is to find problems early, ideally within a day of the invoice being raised, so that the format is corrected before many more invoices are issued in it. Any correction to issued documents is an accounting and tax decision.

For the repeated number, look at the two rows before deciding. Two identical rows with the same date and amount usually mean the register imported the bill twice; two different amounts under the same number mean two documents were issued with one number, which is more serious. For removing genuine duplicate rows without losing legitimate line items, see detect duplicate invoices without deleting legitimate line items.

Excel version for a small register

With invoice numbers in column A, dates in column B and the FY formula in column D:

text
Too long?    =LEN(A2)>16
Bad chars?   =SUMPRODUCT(--ISERROR(FIND(MID(A2,SEQUENCE(LEN(A2)),1),
                 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/")))>0
FY           =IF(MONTH(B2)>=4, YEAR(B2), YEAR(B2)-1)
Repeated?    =COUNTIFS(A:A, A2, D:D, D2)>1

For gaps, sort by series and number, extract the numeric part into a column, and flag any row where it is not the previous row's number plus one within the same series.

Run it on a schedule

Run the check daily or weekly on the register exported from your billing system, and once more before the month's returns are prepared. Keep the output for each run. The same register also feeds the purchase-side matching in reconcile GSTR-2B with the purchase register. Controls like these, which catch problems before a return is filed rather than after, are a practical part of the finance analytics work in our Data Analyst programme.

Frequently asked questions

Can the same invoice number be used again in a new financial year?

Rule 46(b) requires the serial number to be unique for a financial year, which is why most businesses put the year into the number, as in NOI/26-27/0001. Check the treatment for your own registration with your tax adviser before designing a series.

Is a gap in the invoice series always a problem?

Not necessarily, but every gap should have an explanation, such as a cancelled invoice recorded in a cancellation register. The check in this article lists only gaps with no recorded explanation, so the accounts team can look at those.

Can I have separate invoice series for each branch?

The rule allows one or multiple series. Separate series per branch or per document type are common. Run the gap check per series, because gaps only make sense within one sequence.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.