Best Practices

These are the rules that separate integrations that run for years from integrations that page their developers at month-end. Each one is a lesson learned from production EFRIS deployments.

Critical rules

  1. Always register first: never fiscalise a sale with an itemCode that hasn't been registered.
  2. No auto-retry on invoices: if fiscalisation times out, call POST /invoice-details first before retrying. Duplicate fiscalisation = double tax. Setting sellerDetails.isCheckReferenceNo: "1" makes EFRIS reject duplicates of your referenceNo.
  3. Stock before sale: ensure stock is increased before selling, or enable negative stock in the URA portal settings (error 1600: Inventory shortage).
  4. Packaging fields: if a good was registered with pieceScaledValue > 1, send the matching pack/stick values on excise lines and keep quantities consistent so URA stock decrements correctly.
  5. camelCase only: field names must match the official URA names exactly - invoiceNo, buyerTin, goodsCode, sourceBranchId, deemedFlag, vatProjectId, totalWeight, hsCode. snake_case variants are rejected by validation.
  6. Amount consistency: per tax line, netAmount + taxAmount must equal grossAmount (error 1342), and summary totals must equal the sums of taxDetails (excise-duty lines are excluded from the gross total check).

Invoice type quick reference

Invoice type invoiceIndustryCode Extra fields needed
Normal sale 101 (or omit) None
Export goods 102 deliveryTermsCode, per-item totalWeight (mandatory), pieceQty, pieceMeasureUnit, hsCode
Export service 112 deliveryTermsCode
Imported service 104 importServicesSeller block, sellerDetails.branchId
Airline 109 airlineGoodsDetails array
EDC / Fuel 110 edcDetails block (tankNo, pumpNo, nozzleNo required)
Deemed VAT 101 + deemedFlag: "1" per item deemedExemptCode, vatProjectId, vatProjectName per item
Excise duty 101 + exciseFlag: "1" per item categoryId, categoryName, exciseRate, exciseRule, exciseTax per item (+ pack, stick, exciseUnit, exciseCurrency when rule 2)
Debit note any + invoiceType: "4" oriInvoiceId, extend.reason / extend.reasonCode

Recommended integration checklist

  • Cache the system dictionary and commodity categories locally; refresh when /login reports a new dictionary version.
  • Store invoiceId, invoiceNo (FDN), antifakeCode, and qrCode against every ERP invoice record - see Invoice Response.
  • Log the full request and response of every API call. When URA support asks "what did you send?", you want an answer.
  • Treat response: "ERROR" with an EFRIS code in message as a business error to surface to the user, not as a transient failure to retry.
  • Run a daily reconciliation: compare your ERP's fiscalised invoices against POST /query-invoices for the same date range.

Stuck on an integration step? Our engineers reply fast.