einvoice
GST e-invoice (IRN) JSON builder, validator and signed-QR toolkit for Indian businesses that must report B2B invoices to the Invoice Registration Portal (IRP). It converts ERP CSV/Excel exports into schema 1.1 e-invoice JSON through TOML mapping profiles, checks the result against the schema and the portal's business rules before upload, computes IRN hashes for duplicate pre-checks, and decodes and renders the signed QR code.
Unofficial tool. It is not affiliated with or endorsed by GSTN, NIC or the IRP. The schema is a documented subset of version 1.1 and the rule codes are modelled on the portal's, not copied from it. Always validate on the official sandbox before relying on it.
Features
- Mapping from flat CSV/XLSX (one row per invoice line) to
TranDtls,DocDtls,SellerDtls,BuyerDtls,DispDtls,ShipDtls,ItemList,ValDtls,PayDtls,RefDtls,ExpDtls, driven by a TOML profile (column mapping, constants, value maps, date formats). - Optional computation of missing amounts: gross, assessable value, CGST/SGST vs IGST by place of supply, Cess, item totals, invoice totals and round-off, using half-up rounding.
- Validation against a versioned JSON Schema (
einvoice/data/einvoice-1.1.schema.json) and 37 business rules (38 coded checks including schema errors): GSTIN format and checksum, state codes, IGST vs CGST+SGST, HSN length by turnover, UQC units, item and invoice arithmetic with tolerances, date not in the future and inside the reporting window, unique document number per financial year, mandatory fields per supply type (B2B, SEZWP, SEZWOP, EXPWP, EXPWOP, DEXP). - IRN computation (SHA-256 of seller GSTIN + FY + document type + document number) and duplicate detection.
- Signed QR JWT decoding (no signature check by default, RS256 verification with a public key), and QR rendering to PNG, SVG or the terminal.
- Batch mode with a per-issue error CSV, JSON output, and a status/supply type/rule summary.
Install
Python 3.12+.
cd einvoice
uv venv .venv
uv pip install -p .venv/bin/python -e '.[dev]'
source .venv/bin/activate # or call .venv/bin/einvoice directly
Quickstart
einvoice build examples/invoices.csv -p examples/profile.toml -o out/invoices
einvoice validate out/invoices examples/resubmitted.json -c examples/einvoice.toml --errors-csv out/errors.csv
einvoice report out/invoices examples/resubmitted.json -c examples/einvoice.toml
einvoice irn --gstin 29AAGCB7383J1Z4 --fy 2025-26 --type INV --no INV/001
einvoice qr decode examples/irp_response.json --public-key examples/qr_public_key.pem
einvoice qr render examples/irp_response.json -o out/qr.png
The examples contain 50 synthetic invoices (B2B, SEZ, exports, deemed exports, credit notes) with planted errors. They are dated October-November 2025, so examples/einvoice.toml pins "today" to 2025-11-15; without it the reporting-window rule would flag everything.
Usage
build
einvoice build SOURCE.csv|xlsx -p PROFILE.toml -o OUT
OUT ending in .json produces one array file, otherwise a directory with one file per invoice (<seller gstin>_<doc no>.json). Rows are grouped into invoices by the profile's invoice_key. Exit code 1 if any invoice could not be mapped (the others are still written).
$ einvoice build examples/invoices.csv -p examples/profile.toml -o out/invoices
built 50 invoice(s), 0 failed -> out/invoices
Excerpt of a generated file (27XDAJH4657E1ZG_INV_2511_0013.json; the profile left the tax amounts empty, so they were computed):
"ItemList": [
{
"SlNo": "1",
"PrdDesc": "Aerated water 200ml crate",
"IsServc": "N",
"HsnCd": "22021010",
"Qty": 5.0,
"Unit": "XYZ",
"UnitPrice": 600.0,
"TotAmt": 3000.0,
"Discount": 150.0,
"AssAmt": 2850.0,
"GstRt": 28.0,
"IgstAmt": 798.0,
"CesRt": 12.0,
"CesAmt": 342.0,
"TotItemVal": 3990.0
}
],
validate
einvoice validate PATH... [-c CONFIG] [--turnover-crore N] [--window-days N] [--item-tolerance N]
[--invoice-tolerance N] [--today YYYY-MM-DD] [--errors-csv FILE]
[--format text|json] [-q]
PATH is a JSON file (one invoice or an array) or a directory of them. Exit code 1 if any invoice has errors; warnings alone exit 0. Duplicate document numbers are detected across everything in one run.
$ einvoice validate out/invoices examples/resubmitted.json -c examples/einvoice.toml --errors-csv out/errors.csv
(output trimmed to six of the 23 reported invoices)
07ACGHT1434U1ZC_INV_2510_0009.json [INV/2510/0009] INVALID
E 2103 BuyerDtls.Gstin: GSTIN '27ABCDE1234F1Z' does not match the 15-character GSTIN pattern
07ACGHT1434U1ZC_INV_2511_0012.json [INV/2511/0012] WARNINGS
W 2186 ExpDtls: ExpDtls is only meaningful for export supplies, not B2B
07ACGHT1434U1ZC_INV_2510_0015.json [INV/2510/0015] INVALID
E 2161 DocDtls.Dt: Document date 20/11/2025 is after 15/11/2025
07ACGHT1434U1ZC_INV_2511_0039.json [INV/2511/0039] INVALID
E 2123 ItemList[0]: SEZWOP supplies must carry zero CGST/SGST/IGST
27XDAJH4657E1ZG_INV_2511_0007.json [INV/2511/0007] INVALID
E 2104 BuyerDtls.Gstin: GSTIN 36GCBYH5741C1ZA fails the checksum (expected check character E)
29DYUCV7912B1ZG_INV_2511_0017.json [INV/2511/0017] INVALID
E 2162 DocDtls.Dt: Document is 75 days old; reporting window is 30 days
resubmitted.json [INV/2511/0028] INVALID
E 2171 DocDtls.No: Document INV/2511/0028 already used in FY 2025-26 (first seen in 27XDAJH4657E1ZG_INV_2511_0028.json)
51 invoice(s): 28 valid, 2 with warnings, 21 invalid
The error CSV has one row per issue: source,doc_no,status,severity,code,path,message.
report
$ einvoice report out/invoices examples/resubmitted.json -c examples/einvoice.toml
Invoices: 51 valid 28 warnings 2 invalid 21
By supply type
B2B invalid 16 valid 19 warnings 2
DEXP valid 2
EXPWOP valid 2
EXPWP invalid 2 valid 2
SEZWOP invalid 1 valid 1
SEZWP invalid 2 valid 2
By rule
2001 x1 JSON schema violation
2103 x1 Buyer GSTIN has invalid format
2104 x1 Buyer GSTIN checksum is wrong
...
--format json prints the same data as JSON. einvoice rules lists the catalog.
irn
$ einvoice irn --gstin 29AAGCB7383J1Z4 --fy 2025-26 --type INV --no INV/001
72242e6acf3b05a1b7f258e60c7a66dcbda2d4371755cd1677cfa9fba2c03b99
$ einvoice irn out/invoices/27XDAJH4657E1ZG_INV_2511_0028.json examples/resubmitted.json
27XDAJH4657E1ZG_INV_2511_0028.json de1734373b099bb3c530f950329e99d90f8ddb698a9a26a89fbcade279e98374
resubmitted.json de1734373b099bb3c530f950329e99d90f8ddb698a9a26a89fbcade279e98374 DUPLICATE of 27XDAJH4657E1ZG_INV_2511_0028.json
With files, the financial year comes from the document date. --known FILE (one IRN per line) flags ALREADY REGISTERED. Exit code 1 on any duplicate.
qr decode and qr render
SOURCE can be a bare JWT, a file containing one, - for stdin, or an IRP response JSON with a SignedQRCode field (the response's Irn is cross-checked against the QR's).
$ einvoice qr decode examples/irp_response.json --public-key examples/qr_public_key.pem
Header: {"alg": "RS256", "typ": "JWT", "kid": "sample-key"}
SellerGstin 27XDAJH4657E1ZG
BuyerGstin 24MWJBR9785D1ZF
DocNo INV/2511/0001
DocTyp INV
DocDt 03/11/2025
TotInvVal 123456.0
ItemCnt 2
MainHsnCode 84136010
Irn 6666b1189bfd2bf265cb31dcf888683c6d8b16a47efd1a006a4d799cb0fca26c
IrnDt 2025-11-03 11:42:10
Signature: VALID
Without --public-key the signature line reads not checked (no --public-key). A failed verification prints INVALID and exits 1. --json prints header, payload and decoded data.
$ einvoice qr render examples/irp_response.json -o out/qr.png # .png or .svg
wrote out/qr.png
$ einvoice qr render examples/signed_qr.jwt --terminal
Configuration
Validation settings come from a TOML [validate] table (-c), overridable per flag:
| Key | Default | Meaning |
|---|---|---|
turnover_crore |
5.0 | Aggregate turnover; above 5 crore HSN needs 6+ digits, otherwise 4+ |
window_days |
30 | Max age of a document at reporting time; 0 disables the rule |
item_tolerance |
1.0 | Rupee tolerance for item level arithmetic |
invoice_tolerance |
1.0 | Rupee tolerance for invoice level sums |
today |
system date | Reference date for the future/window rules |
Mapping profiles are documented in docs/mapping.md; the rule catalog is in docs/rules.md; algorithms and design decisions are in docs/design.md.
Architecture
src/einvoice/
cli.py click commands (build, validate, report, irn, qr, rules)
mapping.py TOML profile, CSV/XLSX reading, grouping, coercion, amount computation
schema.py schema loading and error formatting
data/ einvoice-1.1.schema.json
rules.py business rules, issue catalog, batch duplicate detection
gstin.py GSTIN format and checksum
irn.py IRN hash
qr.py JWT decode/verify/sign, QR rendering
batch.py loading invoice files, error CSV, summaries
config.py [validate] settings
dates.py portal date handling and financial year
constants.py state codes, UQC, supply types, GST slabs
examples/ sample data, profile, config, signed QR, generator script
tests/ pytest suite
Tests
.venv/bin/python -m pytest -q
134 tests cover each business rule (positive and negative), schema errors, rounding tolerance boundaries, mapping profiles (types, dates, grouping, computation, errors), CSV/XLSX equivalence, IRN vectors, JWT decode/verify/tamper, QR rendering and the CLI end to end. examples/make_examples.py regenerates the sample data (--qr also makes a new key pair and signed QR).
Limitations
- Implements a subset of schema 1.1: no
EwbDtls,AddlDocDtls,BchDtls,AttribDtls; no IRP API client, authentication or upload. - Rules approximate the portal's. Pincode-to-state matching, the master HSN list, per-HSN GST rate tables and GSTIN status lookups need data the tool does not ship.
- The 30-day window is applied to every document (the portal restricts it to larger taxpayers); set
window_days = 0to turn it off. - HSN must be 8 digits for exports; digit-length rules only, not HSN existence.
- Signature verification supports RS256 with a PEM public key only. The IRP's real public key must be obtained from the portal.
- Amounts are floats in the JSON output, rounded to two decimals; comparisons use the configured rupee tolerances.