Generate an IRN
This guide walks through registering an invoice with NIC's IRP, from request through to a stable record on your side.
Prerequisites
- An OAuth client provisioned via the Developer hub (single
gstn-indiascope). - IRP credential uploaded for the seller GSTIN — see Set up IRN credentials. Without this you'll get
400 irp_credentials.not_configured.
Request anatomy
The request body uses the IRP canonical vocabulary in clean PascalCase. Required at the top level:
{
"supplyType": "B2B | SEZWP | SEZWOP | EXPWP | EXPWOP | DEXP",
"documentType": "INV | CRN | DBN",
"invoiceNumber": "...",
"invoiceDate": "YYYY-MM-DD",
"financialYear": "YYYY-YY",
"reverseCharge": false,
"placeOfSupplyStateCode": "NN",
"seller": { ... },
"buyer": { ... },
"lines": [ { ... } ],
"totals": { ... }
}Seller / buyer
Both parties carry the same shape. gstin is required on the seller for B2B; on the buyer it's optional only for B2C-flagged supplies. stateCode is the two-digit NIC code (e.g. 29 Karnataka, 27 Maharashtra).
Lines
Each line carries the assessable value, GST rate, and per-rate split (CGST + SGST for intra-state, IGST for inter-state). Either CGST/SGST or IGST should be non-zero per line — never both — depending on whether the supply crosses state lines.
Totals
Roll up of every line. NIC does its own arithmetic check; if your totals don't match the line sum within tolerance, the call fails with validation_error.
Success path
POST /v1/einvoices HTTP/1.1
Host: api.in.onefinops.com
Authorization: Bearer ...
Idempotency-Key: einvoice-INV-2026-00001{
"id": "einv_01HRXY...",
"object": "einvoice",
"status": "generated",
"irn": "a1b2c3d4...",
"ackNumber": "112324567890123",
"ackDate": "2026-05-04T11:42:18Z",
"signedQrCode": "eyJhbGciOi...",
"signedInvoice": "eyJhbGciOi...",
"generatedAt": "2026-05-04T11:42:18Z",
...
}Store id, irn, and ackNumber against your invoice. Refetch the signed payloads when you need them — don't pin them in your DB.
Queued path
When the IRP is slow or briefly unavailable, you'll get back a record with status: "queued" (or failed_retriable if a soft failure occurred). The IRN is not yet generated, but it will be — our worker keeps retrying.
{
"id": "einv_01HRXY...",
"object": "einvoice",
"status": "queued",
"generatedAt": null
}Two ways to find out it's done:
- Subscribe to the
einvoice.generatedwebhook. Recommended. - Poll the cheap status endpoint:
GET /v1/einvoices/{id}/status— see Poll status.
Failure paths
| Status / code | What happened | Your move |
|---|---|---|
failed_retriable | Transient — IRP timeout, connectivity blip. Worker is retrying. | Wait or webhook. |
failed_terminal | NIC rejected for a domain reason — duplicate, bad GSTIN, fiscal year mismatch. | Read errorCode / errorMessage and upstream.message; fix and resubmit with a new Idempotency-Key. |
400 irp_credentials.not_configured | No credential for seller.gstin. | Upload via Set up IRN credentials. |
422 validation_error | Body shape failed validation. | Inspect details[]. |
409 idempotency.key_reused_mismatch | Same key, different body. | Use a fresh key for a new logical attempt. |
Storage advice
Treat the OneFinOps record as the source of truth:
- Persist
id,irn, your owninvoiceNumber, andstatus. - Refetch
signedInvoice,signedQrCode,ackNumberwhen you need them — they don't expire on our side. - Reconcile asynchronously by listening to
einvoice.generated/einvoice.cancelled/einvoice.failedwebhooks rather than syncing on every request.
Next
Updated about 5 hours ago
