API Reference

Every endpoint, request/response field, transaction type, and status code for the Nami Local API Middleware.

Overview

ItemValue
Base URLhttp://localhost:9099
AuthenticationNone
Request content-typeapplication/json
Response content-typeapplication/json
SSLHTTP by default. If HTTPS has been enabled via the dashboard toggle, use https://localhost:9099 with a self-signed certificate — disable SSL verification in your HTTP client

All transaction endpoints accept POST requests with a JSON body and return a JSON response.

Common fields

txnType — Transaction type

The txnType field selects the transaction. The full set of types, their numeric codes, and ECR ASCII codes is maintained canonically in the Transaction Types reference — that page is the single source of truth, so always check codes against it rather than hard-coding from memory.

The most common values:

CodeTransaction TypePurpose
17Register TerminalRegister the ECR with the terminal — required once before any transaction
0PurchaseStandard card purchase
2RefundRefund an approved transaction
8Cash AdvanceCash to the cardholder
9ReversalReverse the last transaction (within the allowed window)
10ReconciliationEnd-of-day settlement
24Check StatusCheck the ECR ↔ terminal connection

📘

Codes come from the canonical mapping

See Transaction Types for the complete list (including Pre-Authorization 3, Purchase Advice/Capture 4, Pre-Auth Extension 5, Pre-Auth Void 6, Bill Payment 20, Partial Reversal 27, and the parameter-download / settings / reporting types). Codes such as 7, 14, 15, and 16 are intentionally not assigned.

amount — Transaction amount

  • Type: string
  • Format: decimal SAR value, e.g. "10.00" for SAR 10
  • The middleware converts SAR to halalas (× 100) before sending to the terminal
  • Required for: PURCHASE, REFUND, AUTHORIZATION, CASH_ADVANCE, BILL_PAYMENT

date — Transaction date/time

  • Type: string
  • Format: ddmmyyhhmmss (12 digits, no separators)
  • Example: "090625143000" = 09 Jun 2025 at 14:30:00
  • Generate automatically in Postman with this pre-request script:
const now = new Date();
const pad = n => String(n).padStart(2, '0');
const dd = pad(now.getDate());
const mm = pad(now.getMonth() + 1);
const yy = String(now.getFullYear()).slice(-2);
const hh = pad(now.getHours());
const min = pad(now.getMinutes());
const ss = pad(now.getSeconds());
pm.variables.set('currentDate', dd + mm + yy + hh + min + ss);

Then use {{currentDate}} in the request body.

ecrReferenceNo — ECR reference number

  • Type: string
  • Must be exactly 14 digits and begin with a non-zero digit
  • Must be unique per transaction if it is to be used as a reference number. It will also be needed for the Duplicate(Last Transaction) API. Otherwise it can be hardcoded as a static value.
  • The last 6 digits are used in the SHA-256 signature computed by the middleware
  • Example: "12345678901234"

jsonResponse — Response format flag

  • Type: boolean
  • false (default): response contains transaction — the raw semicolon-delimited string from the terminal
  • true: response contains parsedTransaction — a structured JSON object with labelled fields; the raw string is omitted

COM path endpoints

List available COM ports — GET /get-com-ports

Returns the COM ports currently detected on the machine. Only real hardware ports are returned — phantom (driver-only) ports are filtered out.

Response

[
  {
    "device_id": "COM3",
    "description": "USB Serial Device (COM3)",
    "port_number": "3",
    "vendor_id": 6018,
    "product_id": 36865
  }
]

If no device is connected:

[
  {
    "device_id": null,
    "description": "No devices connected",
    "port_number": null,
    "vendor_id": 0,
    "product_id": 0
  }
]

If a previously-connected device is in the middle of reconnecting (e.g. the USB cable was unplugged and replugged, and the middleware is retrying the connection in the background):

[
  {
    "device_id": "COM3",
    "description": "Reconnecting to COM3...",
    "port_number": "3",
    "vendor_id": 0,
    "product_id": 0
  }
]

If the device has been disconnected for longer than com.reconnect.timeout.minutes (default 2) and the middleware is still silently retrying in the background:

[
  {
    "device_id": "COM3",
    "description": "Disconnected — last seen on COM3",
    "port_number": "3",
    "vendor_id": 0,
    "product_id": 0
  }
]

Connect to terminal — POST /com/connect

Establishes a serial connection to the terminal. Must be called before any COM transaction.

Request body

{
  "port": 3,
  "baudRate": "115200",
  "parity": 0,
  "dataBits": 8,
  "stopBits": 1
}
FieldTypeRequiredDescription
portintegerYesCOM port number (e.g. 3 for COM3)
baudRatestringYesOne of: "9600", "19200", "38400", "57600", "115200"
parityintegerYes0 = None, 1 = Odd, 2 = Even
dataBitsintegerYes7 or 8
stopBitsintegerYes1 or 2

Response

{
  "success": true,
  "message": "Connected to COM3 at 115200 baud"
}
FieldTypeDescription
successbooleantrue if connected, false if the port could not be opened
messagestringHuman-readable result or error detail

Run a transaction (COM) — POST /performTransactionCOM

Runs any transaction type over the established COM connection.

🚧

Connect and REGISTER first

/com/connect must have succeeded, and /performTransactionCOM with txnType: REGISTER must have been called, before any financial transaction.

Request body (PURCHASE example)

{
  "txnType": "PURCHASE",
  "amount": "10.00",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234",
  "jsonResponse": true
}

Request body (REGISTER)

{
  "txnType": "REGISTER",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234"
}

Request body (REFUND)

{
  "txnType": "REFUND",
  "amount": "10.00",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234",
  "origTransactionDate": "080625120000",
  "origApproveCode": "123456",
  "jsonResponse": true
}

Request body (REVERSAL)

{
  "txnType": "REVERSAL",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234",
  "prevEcrNo": "12345678901233",
  "jsonResponse": true
}

Request body (RECONCILIATION)

{
  "txnType": "RECONCILIATION",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234",
  "jsonResponse": true
}

Request body (DUPLICATE)

{
  "txnType": "DUPLICATE",
  "date": "090625143000",
  "ecrReferenceNo": "12345678901234",
  "jsonResponse": true
}

Full PaymentRequest field reference

FieldTypeDescription
txnTypestringTransaction type enum name (see table above)
amountstringTransaction amount in SAR (decimal)
datestringDate/time in ddmmyyhhmmss format
ecrReferenceNostringUnique 14-digit ECR reference number
jsonResponsebooleanReturn parsed JSON (true) or raw string (false)
cashBackAmountstringCashback amount in SAR
preAuthAMountstringPre-auth amount in SAR
refundAmountstringRefund amount in SAR
origTransactionDatestringOriginal transaction date (for refund/reversal)
origApproveCodestringOriginal approval code (for refund)
origTransactionAmtstringOriginal transaction amount
cashAdvanceAmtstringCash advance amount
capturestringCapture flag
authAmountstringAuthorisation amount
prevEcrNostringECR reference of previous transaction (for reversal)
rrnNumberstringRetrieval reference number
originalRefundDatestringOriginal refund date
printstringPrint receipt flag
cashRegisterNostringCash register identifier

Response

{
  "statusCode": "200",
  "statusMessage": "Approved",
  "parsedTransaction": {
    "transactionType": "Purchase",
    "approvalCode": "123456",
    "amount": "SAR 10.00",
    "cardNumber": "************1234",
    "terminalId": "8184000200000077"
  },
  "signature": "a3f9...",
  "terminalId": "8184000200000077"
}

See the Response fields section below for all possible fields.


Disconnect (COM) — POST /com/disconnect

Closes the serial port.

Request: No body required.

Response: 0 (integer) on success, 1 on failure.


TCP/IP path endpoints

Run a transaction (TCP/IP) — POST /performTransactionTCPIP

Connects (or reuses an existing connection) and runs a transaction. The connection is established automatically — no separate connect step is needed.

Request body structure

{
  "tcpipcomData": {
    "ip": "192.168.1.100",
    "port": 2345
  },
  "paymentRequest": {
    "txnType": "PURCHASE",
    "amount": "10.00",
    "date": "090625143000",
    "ecrReferenceNo": "12345678901234",
    "jsonResponse": true
  }
}

The paymentRequest object accepts all the same fields as the COM path (see the field table above).

FieldTypeRequiredDescription
tcpipcomData.ipstringYesTerminal IP address
tcpipcomData.portintegerYesTerminal port number
paymentRequestobjectYesTransaction details (same as COM)

Response: Same format as the COM transaction response. ipAddress, portNumber, and connectionStatus are populated for every transaction type (not just PURCHASE/REGISTER).

Disconnect (TCP/IP) — POST /tcp-ip/disconnect

Closes the TCP/IP socket for the specified terminal.

Request body

{
  "tcpipcomData": {
    "ip": "192.168.1.100",
    "port": 2345
  }
}

Response

{
  "Ip": "192.168.1.100",
  "portNumber": 2345,
  "isConnected": "Connection Disconnected",
  "connectionStatus": false
}

Utility endpoints

Cancel active transaction — POST /cancelTransaction

Cancels the currently in-progress transaction (if any).

Request: No body required.

Response

{ "status": "Success", "message": "Transaction cancelled" }

or, if no transaction is active:

{ "status": "Info", "message": "No active transaction to cancel" }

List TCP connections — GET /getExistingConnections

Returns all known TCP/IP connections and their current status.

Response

{
  "192.168.1.100:2345": "Connected",
  "192.168.1.101:2345": "Disconnected"
}

Registration status — GET /registrationStatus

Returns whether a REGISTER transaction has been completed in the current session.

Response

{
  "registered": true,
  "terminalId": "8184000200000077",
  "ip": "192.168.1.100",
  "port": 2345
}

Clear terminal data — POST /clearTerminalData

Clears the in-memory map of registered terminal data (both TCP/IP and COM) and the persisted registration "keymap" used to restore registrations after a restart. Use this to force a clean slate — every connection must REGISTER again.

Request body

{ "confirmClear": true }

Response

{ "status": "Success", "message": "Terminal data map cleared Successfully" }

Connection persistence & auto-reconnect

The middleware remembers the following across a service or PC restart, so operators don't have to manually reconnect every terminal:

  • The last successful COM connection (port, baud rate, parity, data/stop bits, and VID/PID) — restored on startup via SerialConnectionManager, then handed off to the same retry loop used for cable-pull reconnects.
  • The set of active TCP/IP connections (supports multiple parallel terminals, keyed by ip:port) — each is reconnected on a background thread on startup.
  • The Terminal ID registration keymap (which terminal/COM connection has completed REGISTER) — restored into memory on startup so a POS that resumes sending transactions right after a restart doesn't immediately hit REGISTER_REQUIRED.

Self-healing on COM reconnect: every time a COM connection is (re-)established — startup restore, manual Connect, or a cable-pull auto-reconnect — the middleware silently re-registers with the terminal in the background and updates the cached Terminal ID if it has changed (e.g. a different physical unit with the same VID/PID was swapped in). This never surfaces a REGISTER_REQUIRED error to the integrator; a failed silent re-register simply leaves the previous cache entry in place and retries on the next reconnect.

📘

Caveats

  • Persisted Terminal IDs are a best-effort cache. If the terminal itself was rebooted/reset (its own session cleared) while the middleware was down, transactions may still return REGISTER_REQUIRED even though the middleware "remembers" a Terminal ID — call REGISTER again, or POST /clearTerminalData to force a clean slate.
  • TCP/IP auto-reconnect only re-establishes the socket; it does not resend REGISTER. If the terminal's session was reset, send REGISTER again for that connection.

Service health — GET /api/health-status

Returns current service health and connection state. Used by the tray app and the dashboard.

Response

{
  "status": "UP",
  "version": "1.7",
  "uptime": "2h 14m",
  "port": "9099",
  "memoryUsedMb": 145,
  "memoryMaxMb": 512,
  "jSerialCommAvailable": true,
  "availableComPorts": [
    {
      "systemName": "COM3",
      "description": "USB Serial Device (COM3)",
      "vendorId": 6018,
      "productId": 36865
    }
  ],
  "comConnected": true,
  "activeComPort": "COM3",
  "tcpConnections": {
    "192.168.1.100:2345": "Connected"
  },
  "registeredTerminals": [
    {
      "terminalId": "8184000200000077",
      "ipAddress": "192.168.1.100",
      "portNumber": 2345
    }
  ]
}

Response fields

All transaction responses extend a base Response object and include these fields:

FieldTypeDescription
statusCodestringResult code — see table below
statusMessagestringHuman-readable result description
transactionstringRaw semicolon-delimited terminal response (present when jsonResponse: false)
parsedTransactionobjectStructured key-value fields from terminal response (present when jsonResponse: true)
signaturestringSHA-256 signature: SHA256(last6OfEcrRef + terminalId) for transaction authenticity verification
terminalIdstringTerminal ID returned by the terminal
Ip / ipAddressstringIP address (TCP/IP path only)
portNumberintegerPort number (TCP/IP path only)
isConnectedstringConnection status description (disconnect responses)
connectionStatusbooleantrue = still connected, false = disconnected

Status codes

There are two layers of codes in a response, and they come from different places.

Terminal / scheme response codes

The numeric code the terminal returns for a financial transaction (approval, decline, registration, reconciliation, download, etc.) follows the mada scheme and Nami ECR code set — for example 000 Approved, 116 Not sufficient funds, 117 Incorrect PIN, 800 Registration Success, 1003 Do Register, 1008 Terminal Not Registered. These are documented canonically in the Transaction Response Codes reference, which is the single source of truth — look codes up there rather than inferring them.

🚧

Don't confuse these numbers with middleware codes

Several mada codes overlap numerically with unrelated meanings — e.g. 100 is Do not honour (a decline), 101 is Expired card, 102 is Suspected fraud. Always interpret the numeric response code via Transaction Response Codes, not as a generic HTTP-style status.

Middleware transport-level codes

Separately, the middleware itself returns these string codes for conditions that arise before or around terminal communication. They are specific to this middleware and are not part of the scheme code set:

statusCodeMeaningAction
TERMINAL_BUSYTerminal is processing another transactionWait and retry; terminal is single-threaded
TIMEOUTNo response from terminal within the timeout periodCheck cable/network; retry
CANCELLEDTransaction was cancelled by a /cancelTransaction callInformational
INTERRUPTEDThread was interruptedInternal — retry the transaction
ERRORUnexpected exceptionCheck statusMessage and application logs
CableDisconnectedUSB cable was unplugged during the transactionReconnect cable and call /com/connect again
UnableToConnectCould not open the serial portCheck port number, cable, and drivers
REGISTER_REQUIREDREGISTER has not been completed for this sessionCall REGISTER transaction first
INVALID_AMOUNTAmount field is missing or non-numericCorrect the amount field
INVALID_ECR_REFecrReferenceNo is not exactly 14 digitsFix the reference number format

📘

Terminal-originated messages

Human-readable terminal messages (e.g. "Declined", "Card Expired") appear in statusMessage and come directly from the terminal alongside the numeric response code. The numeric code is the authoritative result — resolve it via Transaction Response Codes.