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-india scope).
  • 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:

  1. Subscribe to the einvoice.generated webhook. Recommended.
  2. Poll the cheap status endpoint: GET /v1/einvoices/{id}/status — see Poll status.

Failure paths

Status / codeWhat happenedYour move
failed_retriableTransient — IRP timeout, connectivity blip. Worker is retrying.Wait or webhook.
failed_terminalNIC 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_configuredNo credential for seller.gstin.Upload via Set up IRN credentials.
422 validation_errorBody shape failed validation.Inspect details[].
409 idempotency.key_reused_mismatchSame 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 own invoiceNumber, and status.
  • Refetch signedInvoice, signedQrCode, ackNumber when you need them — they don't expire on our side.
  • Reconcile asynchronously by listening to einvoice.generated / einvoice.cancelled / einvoice.failed webhooks rather than syncing on every request.

Next