All amounts are integers in paise (rupees × 100). The invoice is stored with its lines and the CGST + SGST or IGST split, so it can take an e-invoice IRN and credit notes. Send either `items` — the totals are computed for you, and a `subtotal`, `taxAmount` or `total` sent with them that differs is 400 `totals_mismatch` — or header totals only: then `subtotal` and `total` are required, `taxAmount` must be the GST of ONE rate (0, 1, 5, 12, 18 or 40 %) on `subtotal`, otherwise 400 `tax_not_one_rate` (send `items` for an invoice with several rates), and `total` must equal `subtotal + taxAmount` within 50 paise of rounding to the rupee (kept as the round-off), otherwise 400 `totals_mismatch`. Your `number` is kept as sent; one already used is 409 `invoice_number_exists`. A company with no GSTIN in Settings cannot charge GST (422 `gst_needs_gstin`). Optional `shipTo` when the goods go somewhere other than the client's address (e.g. a site): the e-invoice and e-way bill name it as the ship-to.
Headers
Header
Value
Authorizationrequired
Bearer ck_live_… — your API key from Settings → API keys
Content-Typerequired
application/json
Request body
Field
Type
Description
numberrequired
string
at most 50 characters
clientId
string
A client of your company.
projectId
string
A project of your company.
placeOfSupply
string
2-digit GST state code. Left out: the project site's state, else the client's.pattern ^[0-9]{2}$
items
array of object
The invoice lines. Left out: the header totals become one line.at least 1 itemsat most 500 items
items[].descriptionrequired
string
items[].hsnSac
string
items[].quantityrequired
number
Up to 3 decimals; a 4th is 400.minimum 0
items[].unit
string
items[].ratePaiserequired
integer
Rate per unit in PAISE (rupees × 100). ₹1,500.00 = 150000.
items[].gstRaterequired
integer
GST %.one of 0, 1, 5, 12, 18, 40
items[].cessRate
number
minimum 0
subtotal
integer
Pre-tax subtotal (required without items) in PAISE (rupees × 100). ₹1,500.00 = 150000.
Grand total (required without items) in PAISE (rupees × 100). ₹1,500.00 = 150000.
status
string
one of "Draft", "Sent"default "Draft"
issueDate
string (date)
Issue date as an ISO YYYY-MM-DD string. Left out: today (IST).
dueDate
string (date)
Due date as an ISO YYYY-MM-DD string.
shipTo
object or null
Where the goods go, when not to the client. Omit (or null) to deliver to the client.
shipTo.addressrequired
string
Delivery address. No double quotes.at least 3 charactersat most 100 characters
shipTo.placerequired
string
Delivery city or place. No double quotes.at least 3 charactersat most 50 characters
shipTo.pincoderequired
string
6 digits, not starting with 0.pattern ^[1-9][0-9]{5}$
shipTo.stateCode
string or null
2-digit GST state code. Left out: worked out from the PIN; a state the PIN is not in is 400.pattern ^[0-9]{2}$
Try it
This call changes data. Send runs it in your API sandbox — a practice company with sample data — with a sandbox key, never in your real company. To change real data, run the sample from your own machine with a live key. About the sandbox
400Validation failed, or the totals do not add up.
totals_mismatch
{
"error": "totals_mismatch",
"field": "total",
"message": "The totals sent do not match the items: subtotal 10000000, taxAmount 1800000, total 11800000. Omit them to use the computed figures."
}
tax_not_one_rate
{
"error": "tax_not_one_rate",
"field": "taxAmount",
"message": "taxAmount is not the GST of one rate (0, 1, 5, 12, 18, 40 %) on the subtotal. Send the invoice as line items, each with its own GST rate."
}
validation
{
"error": "subtotal is required when items is not sent"
}
401No `Authorization: Bearer …` header was sent. Or: The key is unknown, revoked or expired — or the company cannot use it.
unauthenticated — No `Authorization: Bearer …` header was sent.
invalid_token — The key is unknown, revoked or expired — or the company cannot use it.
{
"error": "invalid_token",
"message": "API key not recognised, revoked, or expired"
}
402With a sandbox key: the practice company already holds 10,000 invoices (`sandbox_limit`) — reset the sandbox in Settings → API keys. Or the plan / subscription does not allow the API.
sandbox_limit
{
"error": "sandbox_limit",
"message": "The sandbox holds at most 10,000 invoices. Reset the sandbox in Settings → API keys.",
"resource": "invoices",
"used": 10000,
"limit": 10000
}
plan_required
{
"error": "plan_required",
"message": "API access requires the Scale plan. This workspace is on Growth.",
"currentPlan": "growth",
"requiredPlan": "scale"
}
access_expired
{
"error": "access_expired",
"reason": "payment_failed",
"message": "Workspace does not currently have an active Scale subscription."
}
403The request came from an IP address that is not on the key's allowlist. Or: The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE.
ip_not_allowed — The request came from an IP address that is not on the key's allowlist.
{
"error": "ip_not_allowed",
"message": "Source IP 203.0.113.7 is not on the key's allowlist."
}
insufficient_scope — The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE.
{
"error": "insufficient_scope",
"message": "This API key does not have the 'write' scope.",
"required": "write",
"granted": [
"read"
]
}
404`clientId` or `projectId` is not one of your company's.
client_not_found
{
"error": "client_not_found"
}
project_not_found
{
"error": "project_not_found"
}
409That invoice number is already used in your company.
{
"error": "invoice_number_exists"
}
422Your company has no GSTIN, so the invoice cannot charge GST. `code` is `gst_needs_gstin`; `field` names the line.
{
"error": "Your company has no GSTIN in Settings → Company, so this bill cannot charge GST. Set GST to 0 %, or add your GSTIN first.",
"code": "gst_needs_gstin",
"field": "items.0.gstRate"
}
429More than 120 requests in a minute with this key (30 with a sandbox key).
rate_limited — More than 120 requests in a minute with this key (30 with a sandbox key).
{
"error": "Too many requests. Please try again later."
}
500Something failed on our side.
internal — Something failed on our side.
{
"error": "internal"
}
Errors
Errors every call can return
Status
Code
Meaning
What to do
401
unauthenticated
No `Authorization: Bearer …` header was sent.
Send your API key as `Authorization: Bearer ck_live_…` on every request.
401
invalid_token
The key is unknown, revoked or expired — or the company cannot use it.
Check you copied the whole key. Make a new one in Settings → API keys if it was revoked or has expired.
403
ip_not_allowed
The request came from an IP address that is not on the key's allowlist.
Call from an allowed address, or change the key's IP allowlist in Settings → API keys.
402
plan_required
The company is not on the Scale plan, which includes the API.
The Owner moves the company to Scale in Settings → Billing.
402
access_expired
The subscription is not active (trial over, payment failed, cancelled, paused, locked or closing). `reason` says which.
The Owner pays or renews in Settings → Billing; calls work again at once.
429
—
More than 120 requests in a minute with this key (30 with a sandbox key).
Wait the number of seconds in the `Retry-After` header. Every reply carries `RateLimit-Remaining`, so you can slow down before this happens.
403
insufficient_scope
The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE.
Make a key with the scope you need in Settings → API keys.
500
internal
Something failed on our side.
Retry with exponential backoff. If it keeps happening, email [email protected] with the time of the call.