Poll status / async retries

NIC's IRP isn't always fast. We never block your request waiting on a slow upstream — instead, the API returns a queued or failed_retriable record and our worker keeps trying in the background.

State machine

                     ┌──────────► generated ──► (cancelled, optional)
pending → queued ────┤
                     ├──────────► failed_retriable ──► generated   (retry succeeded)
                     │                            └──► failed_terminal (gave up)
                     └──────────► failed_terminal (rejected by NIC immediately)
                     
                                  not_applicable  (record exists for non-IRN flow)
StatusMeaning
pendingRecord created, not yet sent to IRP. Transient.
queuedSent to IRP; awaiting acknowledgement. Worker may retry.
generatedIRN issued. Final happy state.
cancelledIRN was cancelled (within 24h).
failed_retriableTransient failure (timeout, 5xx). Worker is retrying.
failed_terminalNIC rejected for a domain reason; no further retries.
not_applicableRecord exists for an invoice that doesn't require an IRN.

generated, cancelled, failed_terminal, and not_applicable are terminal — once you see them, the state won't change again on its own.

The cheap status endpoint

Polling the full GET /v1/einvoices/{id} works, but pulls the signed invoice + QR on every poll — bytes you don't need until status flips. Use the lightweight projection instead:

GET /v1/einvoices/{id}/status HTTP/1.1
Host: api.in.onefinops.com
Authorization: Bearer ...
{
  "id": "einv_01HRXY...",
  "object": "einvoice.status",
  "status": "queued",
  "irn": null,
  "errorCode": null,
  "errorMessage": null,
  "retryCount": 1,
  "nextRetryAt": "2026-05-04T11:43:18Z"
}

Once status flips to generated (or another terminal), do one full GET to capture signedInvoice, signedQrCode, ackNumber, etc.

Polling strategy

  1. Prefer webhooks. Subscribe to einvoice.generated and einvoice.failed; our worker will tell you the moment state flips.
  2. If you must poll, back off. Start at 5–10 seconds and double, capped at ~60 seconds.
  3. Stop once you hit a terminal state. Continuing to poll after generated wastes both ends.
  4. Trust nextRetryAt. If the field is set, our worker is going to retry at that timestamp anyway — polling more often than that buys nothing.

Detecting "stuck" requests

A record sitting in queued or failed_retriable past retryCount: 5 and several minutes is an indicator either NIC is having a bad day or the credential / payload has a problem the worker keeps hitting. Email [email protected] with the id and requestId and we'll dig in.