API Reference
Every endpoint, request/response field, transaction type, and status code for the Nami Local API Middleware.
Overview
| Item | Value |
|---|---|
| Base URL | http://localhost:9099 |
| Authentication | None |
| Request content-type | application/json |
| Response content-type | application/json |
| SSL | HTTP 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
txnType — Transaction typeThe 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:
| Code | Transaction Type | Purpose |
|---|---|---|
| 17 | Register Terminal | Register the ECR with the terminal — required once before any transaction |
| 0 | Purchase | Standard card purchase |
| 2 | Refund | Refund an approved transaction |
| 8 | Cash Advance | Cash to the cardholder |
| 9 | Reversal | Reverse the last transaction (within the allowed window) |
| 10 | Reconciliation | End-of-day settlement |
| 24 | Check Status | Check the ECR ↔ terminal connection |
Codes come from the canonical mapping
See Transaction Types for the complete list (including Pre-Authorization
3, Purchase Advice/Capture4, Pre-Auth Extension5, Pre-Auth Void6, Bill Payment20, Partial Reversal27, and the parameter-download / settings / reporting types). Codes such as7,14,15, and16are intentionally not assigned.
amount — Transaction amount
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
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
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
jsonResponse — Response format flag- Type:
boolean false(default): response containstransaction— the raw semicolon-delimited string from the terminaltrue: response containsparsedTransaction— a structured JSON object with labelled fields; the raw string is omitted
COM path endpoints
List available COM ports — GET /get-com-ports
GET /get-com-portsReturns 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
POST /com/connectEstablishes 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
}
| Field | Type | Required | Description |
|---|---|---|---|
port | integer | Yes | COM port number (e.g. 3 for COM3) |
baudRate | string | Yes | One of: "9600", "19200", "38400", "57600", "115200" |
parity | integer | Yes | 0 = None, 1 = Odd, 2 = Even |
dataBits | integer | Yes | 7 or 8 |
stopBits | integer | Yes | 1 or 2 |
Response
{
"success": true,
"message": "Connected to COM3 at 115200 baud"
}
| Field | Type | Description |
|---|---|---|
success | boolean | true if connected, false if the port could not be opened |
message | string | Human-readable result or error detail |
Run a transaction (COM) — POST /performTransactionCOM
POST /performTransactionCOMRuns any transaction type over the established COM connection.
Connect and REGISTER first
/com/connectmust have succeeded, and/performTransactionCOMwithtxnType: REGISTERmust 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
| Field | Type | Description |
|---|---|---|
txnType | string | Transaction type enum name (see table above) |
amount | string | Transaction amount in SAR (decimal) |
date | string | Date/time in ddmmyyhhmmss format |
ecrReferenceNo | string | Unique 14-digit ECR reference number |
jsonResponse | boolean | Return parsed JSON (true) or raw string (false) |
cashBackAmount | string | Cashback amount in SAR |
preAuthAMount | string | Pre-auth amount in SAR |
refundAmount | string | Refund amount in SAR |
origTransactionDate | string | Original transaction date (for refund/reversal) |
origApproveCode | string | Original approval code (for refund) |
origTransactionAmt | string | Original transaction amount |
cashAdvanceAmt | string | Cash advance amount |
capture | string | Capture flag |
authAmount | string | Authorisation amount |
prevEcrNo | string | ECR reference of previous transaction (for reversal) |
rrnNumber | string | Retrieval reference number |
originalRefundDate | string | Original refund date |
print | string | Print receipt flag |
cashRegisterNo | string | Cash 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
POST /com/disconnectCloses 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
POST /performTransactionTCPIPConnects (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).
| Field | Type | Required | Description |
|---|---|---|---|
tcpipcomData.ip | string | Yes | Terminal IP address |
tcpipcomData.port | integer | Yes | Terminal port number |
paymentRequest | object | Yes | Transaction 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
POST /tcp-ip/disconnectCloses 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
POST /cancelTransactionCancels 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
GET /getExistingConnectionsReturns 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
GET /registrationStatusReturns 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
POST /clearTerminalDataClears 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 hitREGISTER_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_REQUIREDeven though the middleware "remembers" a Terminal ID — callREGISTERagain, orPOST /clearTerminalDatato 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, sendREGISTERagain for that connection.
Service health — GET /api/health-status
GET /api/health-statusReturns 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:
| Field | Type | Description |
|---|---|---|
statusCode | string | Result code — see table below |
statusMessage | string | Human-readable result description |
transaction | string | Raw semicolon-delimited terminal response (present when jsonResponse: false) |
parsedTransaction | object | Structured key-value fields from terminal response (present when jsonResponse: true) |
signature | string | SHA-256 signature: SHA256(last6OfEcrRef + terminalId) for transaction authenticity verification |
terminalId | string | Terminal ID returned by the terminal |
Ip / ipAddress | string | IP address (TCP/IP path only) |
portNumber | integer | Port number (TCP/IP path only) |
isConnected | string | Connection status description (disconnect responses) |
connectionStatus | boolean | true = 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.
100is Do not honour (a decline),101is Expired card,102is 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:
statusCode | Meaning | Action |
|---|---|---|
TERMINAL_BUSY | Terminal is processing another transaction | Wait and retry; terminal is single-threaded |
TIMEOUT | No response from terminal within the timeout period | Check cable/network; retry |
CANCELLED | Transaction was cancelled by a /cancelTransaction call | Informational |
INTERRUPTED | Thread was interrupted | Internal — retry the transaction |
ERROR | Unexpected exception | Check statusMessage and application logs |
CableDisconnected | USB cable was unplugged during the transaction | Reconnect cable and call /com/connect again |
UnableToConnect | Could not open the serial port | Check port number, cable, and drivers |
REGISTER_REQUIRED | REGISTER has not been completed for this session | Call REGISTER transaction first |
INVALID_AMOUNT | Amount field is missing or non-numeric | Correct the amount field |
INVALID_ECR_REF | ecrReferenceNo is not exactly 14 digits | Fix the reference number format |
Terminal-originated messages
Human-readable terminal messages (e.g. "Declined", "Card Expired") appear in
statusMessageand come directly from the terminal alongside the numeric response code. The numeric code is the authoritative result — resolve it via Transaction Response Codes.
