Reading tax legs
Every line item carries three per-leg tax amounts plus a derived tax_percentage. The amounts are the source of truth — don't re-derive them from rate × taxable on your side.
The four fields
"line_items": [
{
"name": "Consulting services",
"quantity": 1,
"rate": 10000.00,
"discount": 0,
"taxable_value": 10000.00,
"cgst_amount": 900.00,
"sgst_amount": 900.00,
"igst_amount": 0.00,
"cess_amount": 0.00,
"tax_percentage": 18,
"item_total": 11800.00
}
]| Field | Meaning |
|---|---|
cgst_amount | Central GST — intra-state supplies |
sgst_amount | State/UT GST — intra-state supplies |
igst_amount | Integrated GST — inter-state supplies |
cess_amount | GST compensation cess — on specific HSN codes |
taxable_value | Pre-tax line total (after discount, before any tax) |
tax_percentage | Derived: (cgst + sgst + igst) / taxable × 100, snapped to integer ±0.1 |
Intra vs inter
For a single line you'll see EITHER CGST+SGST OR IGST, never both. If place_of_supplymatches the supplier branch's state → intra (CGST+SGST split half-half). Otherwise → inter (IGST = full rate).
// Intra-state (Karnataka supplier → Karnataka customer @ 18%)
{ "cgst_amount": 900, "sgst_amount": 900, "igst_amount": 0, "tax_percentage": 18 }
// Inter-state (Karnataka supplier → Maharashtra customer @ 18%)
{ "cgst_amount": 0, "sgst_amount": 0, "igst_amount": 1800, "tax_percentage": 18 }If you ever see both populated on the same line, it's a data anomaly — open a ticket with the line_item_id.
Rate snapping
tax_percentageis computed from real per-leg amounts (not the line's stored rate field, which can be stale on legacy data). The raw division can produce values like 17.99 due to rounding — we snap to nearest integer when within ±0.1, so you get 18 back. Fractional rates like 12.5 stay unchanged.
Why this matters: GSTR-1 buckets supplies by integer rate (5, 12, 18, 28). A fractional rate on a line would land in "no bucket" on the return.
Don't reconstruct legs
Don't do this on your side:
// WRONG — re-derived from rate, loses rounding precision and per-line cess
const cgst = taxable * tax_percentage / 200;
const sgst = taxable * tax_percentage / 200;The amount columns are already correct, rounded to 2dp, and include any per-line cess. Use them directly.
Totals
At the document level you also get totals:
{
"sub_total": 10000.00,
"tax_total": 1800.00,
"total": 11800.00,
"round_off": 0
}total = sub_total + tax_total + round_off. Round-off absorbs sub-rupee differences when the org's roundInvoiceTotals preference is on.