API
The proxy API is intentionally smaller and easier to consume than raw OPI. You send JSON requests to the proxy, and the proxy handles the terminal-side OPI conversation.
Quick Links
API Basics
- terminal-scoped operations use
terminalAlias - stored follow-up actions use
transactionId - almost all routes require a bearer token
GET /healthis intentionally public- mutating routes support
Idempotency-Keyfor safe retries
Authentication
Use:
Authorization: Bearer <token>Health
GET /health
Example response:
{
"ok": true,
"service": "nexi-opi-proxy",
"version": "1.0.5"
}Terminal Routes
GET /terminals/:terminalAlias/info
Returns terminal information, parsed info-receipt data, and discovered payment methods when available.
GET /terminals/:terminalAlias/status
Returns terminal status and cached reachability information.
GET /terminals/:terminalAlias/ping
Sends four ICMP ping packets to the configured terminal IP. A reachable terminal returns 204 No Content; an unreachable terminal returns 503 Service Unavailable with no body.
Use this route as a simple network presence check. It does not open an OPI session or prove that the terminal application is active; use /status when you need the live OPI status payload.
GET /terminals/:terminalAlias/receipts/latest
Returns the latest stored receipt bundle for the terminal. That may come from a transaction or from a terminal service operation.
GET /terminals/:terminalAlias/receipts?limit=5
Returns the most recent stored receipt bundles for the terminal across transaction and terminal service flows.
POST /terminals/:terminalAlias/payment
Starts a sale or a preauthorisation.
Default sale:
{
"reference": "ORDER-100045",
"amount": 24.90,
"currency": "CHF"
}Preauthorisation uses the same route with autoCapture=false:
{
"reference": "ORDER-100046",
"amount": 120.00,
"currency": "CHF",
"autoCapture": false
}Cashback also uses the same route. Send the total EFT amount as amount and the cash withdrawal as cashback. The proxy forwards OPI TotalAmount = amount and CashBackAmount = cashback.
{
"reference": "ORDER-100047",
"amount": 50.00,
"cashback": 20.00,
"currency": "CHF"
}cashback must be less than or equal to amount.
For unattended IM15 and IM30 proxy-mode flows, the proxy also stores receipts by default and returns receipt metadata through the receipt endpoints.
For merchant setups approved for Forced Acceptance, the payment request can include forceAcceptance=true. The proxy forwards this to OPI as ForceAcceptance in POSdata.
{
"reference": "ORDER-100048",
"amount": 24.90,
"currency": "CHF",
"forceAcceptance": true
}Use this only when the merchant and terminal setup supports Forced Acceptance and normal online authorisation cannot be reached. The integrator is responsible for stopping forceAcceptance once normal authorisation is available again. The proxy returns forceAcceptance on payment transaction responses when it was requested, and returns systemAvailable: true when a Forced Acceptance payment reports terminalDeclineReason: 0; at that point, send forceAcceptance: false or omit the parameter on later payments. See Offline Payments for the Own Risk model.
POST /terminals/:terminalAlias/reversal
Starts a direct terminal-side PaymentReversal.
The proxy does not send the original approval code. If the proxy has a previous successful reversible transaction for the terminal, it reuses that amount. If no stored proxy transaction exists, the proxy can still forward the reversal request to the terminal.
POST /terminals/:terminalAlias/refund
Starts a direct terminal-side refund.
Example:
{
"reference": "REFUND-100045",
"amount": 24.90,
"currency": "CHF"
}POST /terminals/:terminalAlias/reprint
Requests a reprint of the last receipt through the terminal.
POST /terminals/:terminalAlias/transmit
Triggers OPI TransmitTrx.
Use this route as part of recovery when a merchant setup has pending offline or Forced Acceptance transactions that must be submitted or transmitted after connectivity returns. See Offline Payments for the business and risk model.
POST /terminals/:terminalAlias/config
Triggers OPI ContactTMS and returns the parsed terminal configuration payload from the accept host ep2.
This route can be called with an empty body, or with responseMode when the client should not wait for the terminal operation.
POST /terminals/:terminalAlias/init
Triggers OPI ContactAcq and returns the parsed merchant-contract initialisation and supported brand payload from the accept host ep2.
This route can be called with an empty body, or with responseMode when the client should not wait for the terminal operation.
POST /terminals/:terminalAlias/activate
Activates or logs in the terminal when the target setup requires it.
By default, the proxy returns early if its cached state already says the terminal is active. Send { "force": true } to bypass the cached state and send the OPI Login request anyway. AlreadyActivated is treated as a successful state confirmation.
POST /terminals/:terminalAlias/deactivate
Deactivates or logs out the terminal when the target setup requires it.
By default, the proxy returns early if its cached state already says the terminal is inactive. Send { "force": true } to bypass the cached state and send the OPI LogOff request anyway. AlreadyDeactivated is treated as a successful state confirmation.
POST /terminals/:terminalAlias/abort
Sends an abort request for an in-progress card flow.
POST /terminals/:terminalAlias/close
Triggers OPI CloseDay and performs the final balance with logoff at the end.
This route can be called with an empty body. In async mode, the proxy returns an empty HTTP 202 with Preference-Applied: respond-async.
POST /terminals/:terminalAlias/reset
Requests a terminal restart.
Payment Example
POST /terminals/front-counter/payment
Authorization: Bearer <token>
Content-Type: application/json{
"reference": "ORDER-100045",
"amount": 24.90,
"currency": "CHF",
"paymentMethods": ["visa", "mastercard", "twint"],
"receiptHandling": {
"customer": "external",
"merchant": "external"
}
}Important notes:
workstationIdis the OPIWorkstationID. Configure it as a stable, unique POS/ECR identity per checkout or terminal context. If omitted, the proxy uses the terminal alias.autoCapture=truemeans saleautoCapture=falsemeans preauthorisationClerkIdandShiftNumberare optional and are omitted from OPI if not suppliedpaymentMethodslimits which explicitly filterable brands can be processed for a request. It does not change terminal UI branding.- the example
paymentMethodsvalues reflect brands officially tested through the OPI Proxy as of today - other brands relevant to the Swiss market can still work through OPI even if they are not yet available for request-level filtering through
paymentMethods receiptHandling.customer=externalandreceiptHandling.merchant=externalsend receipt output to the proxy for storage underdata/receipts; in this mode, the ECR is responsible for asking whether the customer receipt should be printed, shown, sent, or skippedreceiptHandling.customer=localandreceiptHandling.merchant=locallet terminals with local printers handle those copies on the terminal- on validated IM30 unattended proxy-mode payment flows, the terminal sends
DeliveryBox, the proxy acknowledges it withSuccess, and the terminal continues with its own delivery-success messaging /configand/initreturn parsed terminal service details, including status data and any supported brand data returned by the terminal- service routes that print slips return receipt metadata and file paths, not the full receipt body
- tips and DCC are terminal-side configuration topics, not public JSON request flags
Async Responses
By default we recommend RESPONSE_MODE=async. With RESPONSE_MODE=async, request body "responseMode": "async", or Prefer: respond-async, long-running routes return immediately after the operation is accepted.
Payment-style async response:
{
"transactionId": "{transactionId}",
"status": "pending",
"terminalAlias": "front-counter"
}The response includes Location: /transactions/{transactionId}. Poll GET /transactions/:transactionId for the source-of-truth result.
Service-style operations such as /config, /init, /transmit, /close, /reprint, /abort, and /reset return an empty HTTP 202 with Preference-Applied: respond-async.
pending means the terminal operation has not fully ended. unknown means the proxy could not safely determine the final payment or terminal result.
Mutating routes also support Idempotency-Key. Reusing the same key with the same request method, route, async preference, and JSON body returns the same proxy response again. Reusing the same key with different request content returns HTTP 409.
If the terminal is already locked by another long-running operation, the proxy returns 409 with the active operation:
{
"error": {
"code": "BUSY",
"message": "Terminal already has an active operation."
},
"terminalAlias": "front-counter",
"activeOperation": "payment"
}Receipt Routes
The canonical receipt lookup is transaction-based:
GET /transactions/:transactionId/receipts
That is the stable route to use once you have a stored transactionId.
The terminal receipt routes are convenience lookups across the latest stored terminal receipts, including service-operation slips:
GET /terminals/:terminalAlias/receipts/latestGET /terminals/:terminalAlias/receipts?limit=5
Transaction Routes
GET /transactions/:transactionId
Reads a stored transaction.
POST /transactions/:transactionId/capture
Captures a stored preauthorisation.
Example:
{
"amount": 120.00
}The resulting capture transaction includes parentTransactionId, pointing back to the stored preauthorisation.
POST /transactions/:transactionId/cancel
Cancels a stored preauthorisation.
POST /transactions/:transactionId/refund
Refunds a stored sale or captured transaction.
Example:
{
"amount": 25.00
}Follow-up transactions use parentTransactionId to point to the transaction they were created from. The public API does not expose a separate rootTransactionId; if no parent exists, the transaction stands on its own.
PATCH /transactions/:transactionId
Admin-only recovery route for manual transaction correction.
Response Style
Successful responses return JSON data plus requestId, except for empty async 202 responses on service-style operations.
Error responses use this shape:
{
"error": {
"code": "SOME_CODE",
"message": "Human-readable message"
},
"requestId": "..."
}