GST invoice API: invoice JSON to tax invoice PDF

Send invoice data as JSON and get a GST tax invoice PDF back, with CGST/SGST or IGST chosen from the place of supply, paise-exact rounding and GSTIN checksum validation.

What it does

  • POST /v1/process with X-Convert-Type: gst-invoice and a JSON body {"template": "gst-invoice", "data": {...}}. The response is the PDF.
  • Line items with HSN/SAC, quantity, unit, rate, GST rate and an amount or percentage discount; taxable value, tax per head, totals and the amount in words (lakh/crore numbering) are computed for you.
  • Optional UPI payment QR (upi_id), reverse-charge flag, export / LUT invoices with zero GST, and pass-through of an IRN, acknowledgement and signed QR from your GSP.
  • Long invoices span pages with the table header repeated and an "Invoice <number> · Page x of y" footer. Certificate and receipt templates use the same call.
  • 2 credits per invoice; included from the Starter plan.

curl

API=${SIGLI_API_URL:-https://api.sigli.dev}

curl -sS "$API/v1/process" \
  -H "X-API-Key: $SIGLI_API_KEY" \
  -H "X-Convert-Type: gst-invoice" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "gst-invoice",
    "data": {
      "seller": { "name": "Shree Ganesh Traders Private Limited", "gstin": "29AABCS1429B1ZQ" },
      "buyer": { "name": "Annapurna Supermarkets LLP", "gstin": "27AAKFA2345C1ZU" },
      "invoice_number": "SGT/2026-27/0419",
      "invoice_date": "2026-10-22",
      "line_items": [
        { "description": "Toor Dal 1 kg", "hsn": "0713", "qty": 7, "unit": "KG",
          "rate": 4999, "gst_rate": 5, "discount": 5 }
      ],
      "round_off": true
    }
  }' \
  -o invoice.pdf

Place of supply: CGST + SGST or IGST

The supplier's state is the first two digits of seller.gstin. The place of supply is resolved in this order:

  1. place_of_supply — a state name ("Karnataka"), its code ("29") or both ("Karnataka (29)"); a name and code that disagree are rejected
  2. the state code in buyer.gstin
  3. buyer.state
  4. the seller's own state (over-the-counter sale)

Same state: CGST + SGST. Different states: IGST. A seller registered in a Union Territory without a legislature (codes 04, 26, 31, 35, 38, 97) gets UTGST instead of SGST. export: true or a lut_number zeroes GST.

Tax math, to the paisa

Money is computed in integer paise, rounding half up. Line amount = qty × rate; the discount comes off before tax; IGST is rounded once on the full rate, while CGST and SGST are each rounded on half the rate. Totals are sums of the rounded line values, and round_off: true rounds the payable amount to the rupee with an explicit "Round off" line. The same three items, billed within Karnataka and then to Maharashtra:

SupplyTaxTotalAmount in words
Karnataka → Karnataka (CGST + SGST)CGST 992.00 + SGST 992.00₹38,412.48Indian Rupees Thirty Eight Thousand Four Hundred Twelve and Forty Eight Paise Only
Karnataka → Maharashtra (IGST)IGST 1,983.99₹38,412.47Indian Rupees Thirty Eight Thousand Four Hundred Twelve and Forty Seven Paise Only

The one-paisa difference is the 18% delivery line: IGST on ₹1,250.50 is ₹225.09, while CGST and SGST are ₹112.55 each.

Python

Produces both invoices from the table above.

# Render the same GST invoice for a buyer in the seller's state (CGST + SGST)
# and in another state (IGST). Sigli works out the tax head from the GSTINs.
# pip install requests
import copy
import os

import requests

API = os.environ.get("SIGLI_API_URL", "https://api.sigli.dev")
HEADERS = {"X-API-Key": os.environ["SIGLI_API_KEY"], "X-Convert-Type": "gst-invoice"}

invoice = {
    "seller": {
        "name": "Shree Ganesh Traders Private Limited",
        "gstin": "29AABCS1429B1ZQ",  # 29 = Karnataka
        "address": "Plot 42, KIADB Industrial Area, Peenya 2nd Stage, Bengaluru 560058",
    },
    "buyer": {"name": "Annapurna Supermarkets LLP", "gstin": "29AAPFU0939F1ZR"},
    "invoice_number": "SGT/2026-27/0418",
    "invoice_date": "2026-10-22",
    "line_items": [
        {"description": "Basmati Rice 25 kg bag", "hsn": "1006", "qty": 2,
         "unit": "BAG", "rate": 99.99, "gst_rate": 5, "discount_percent": 5},
        {"description": "Toor Dal 1 kg", "hsn": "0713", "qty": 7, "unit": "KG",
         "rate": 4999, "gst_rate": 5, "discount": 5},
        {"description": "Delivery charges", "hsn": "996511", "qty": 1,
         "rate": 1250.50, "gst_rate": 18},
    ],
}

# Same invoice, buyer registered in Maharashtra (27): inter-state, so IGST.
inter_state = copy.deepcopy(invoice)
inter_state["buyer"] = {"name": "Annapurna Supermarkets LLP (Pune)",
                        "gstin": "27AAKFA2345C1ZU"}

for name, data in [("intra-state", invoice), ("inter-state", inter_state)]:
    resp = requests.post(
        f"{API}/v1/process",
        headers=HEADERS,
        json={"template": "gst-invoice", "data": data},
        timeout=120,
    )
    if resp.status_code == 400:  # e.g. bad GSTIN checksum, unknown HSN format
        raise SystemExit(resp.json()["message"])
    resp.raise_for_status()
    with open(f"invoice-{name}.pdf", "wb") as f:
        f.write(resp.content)
    print(f"invoice-{name}.pdf", len(resp.content), "bytes")

Validation before rendering

Bad input is a 400 bad_request naming the field, so a wrong invoice is never printed:

  • GSTIN format, state code and GSTN check character — e.g. data.seller.gstin check digit is invalid — verify the GSTIN
  • GST rate one of 0, 0.1, 0.25, 1, 1.5, 3, 5, 6, 7.5, 12, 18, 28, 40
  • HSN/SAC of 4, 6 or 8 digits; IRN of 64 hex characters
  • Unknown line-item keys (a misspelt discout) are rejected instead of silently ignored

Sigli renders the invoice; it does not register it with the IRP. Any other HTML layout can go through the HTML to PDF API.

FAQ

How does Sigli choose CGST + SGST or IGST?

The supplier's state is the first two digits of seller.gstin. The place of supply is place_of_supply if given, else the state in buyer.gstin, else buyer.state, else the seller's state. Same state means CGST + SGST; different states mean IGST.

Can it generate an IRN or e-invoice QR?

No. Sigli renders the invoice; it does not register it with the IRP. If your GSP gives you an IRN, ack number and signed QR, pass them (irn, ack_no, ack_date, signed_qr) and they are printed as supplied.

Why can CGST + SGST differ from IGST by one paisa?

CGST and SGST are each rounded to the paisa on their own half of the rate, while IGST is rounded once on the full rate. The totals are the sums of the rounded line values.

Try it on your own files

New workspaces start with a 14-day Starter trial (3,000 credits). Create an API key on the API Keys page and run the examples above as they are.