Remote API overview

The Scan to Pay Remote API — the protocol your wallet app uses to look up QR codes, push purchases to the acquirer, and read transaction state.

The Scan to Pay Remote API is the protocol that wallet apps — banking apps, fintech wallets, co-branded experiences — use to participate in the Scan to Pay network. Your wallet handles the customer relationship, the card vault, and the UI; the Remote API handles the QR engine, merchant lookup, transaction routing, and acquirer communication.

This page is the entry point. For the call-by-call request flow, see Purchase flow. For the rules that govern the integration, see Remote API — business rules.


What the Remote API does

The API exposes seven operations grouped into four logical capabilities.

CapabilityOperationEndpoint
Lookup the QR / valueResolve a scanned code into a merchant, amount, accepted cards, and auth methodPOST /generateTransactionId
PaySubmit card + auth data → acquirerPOST /purchaseTx
Initiate 3DS authentication if the card requires itPOST /secureCodeLookup
Track the resultPoll for the final statePOST /transactionState/{transactionId}
Show last 10 transactions in the wallet UIPOST /transactionHistory
Help the UILook up card requirements by BIN (CVV needed? expiry needed?)POST /binLookup
Get the VAS menu (airtime, bills, donations) for your walletGET /menu

Base URLs

EnvironmentBase URL
Sandbox / QAhttps://qa.scantopay.io/pluto/remote/v4
Productionhttps://scantopay.live/pluto/remote/v4

All endpoints sit under the /remote/v4 prefix. Older /v2 and /v3 prefixes are deprecated — new integrations must use v4.


Authentication

The Remote API supports two authentication modes — your manager profile is configured for one or the other (not both at once).

ModeHeaderWhen to use
Basic AuthAuthorization: Basic <base64(username:password)>Historical default. Still supported for existing integrations.
JWT Bearer + PKIAuthorization: Bearer <jwt>Recommended for new integrations. PKI-based mutual auth with challenge/response.

For the PKI handshake, the JWT TTL behaviour, and curl-level examples, see Authentication.

Every request must:

  • Use HTTPS (TLS 1.2+)
  • Set Content-Type: application/json
  • Include valid Basic or Bearer credentials

Common request fields

Every wallet purchase request shares a small set of identifying fields. Understanding these up front saves time as you read the per-operation pages.

FieldUsed onDescription
msisdnAlmost every requestWallet owner's mobile number in international format (e.g. 27832006283). The wallet's primary identifier.
valueLookup, PurchaseThe QR / 10-digit code / URL that was scanned. URL-encoded if you're passing it on a query string.
transactionIdPurchase, State, SecureCodeLong integer returned by generateTransactionId. Carries the merchant + amount + auth context through the flow.
cardNumber, expiryDate, account, cvv2PurchaseCard data your wallet vaulted at provisioning time.
pinData or secureCodeDataPurchaseEither an encrypted PIN block (AMT) or 3D Secure verification fields. Required, mutually exclusive.
deviceDataLookup, PurchaseOptional device fingerprint: OS, app version, GPS, IP. Recommended for fraud-screening.

Response status convention

Every Remote API call returns a JSON object with a top-level status field. The two values you'll see on /purchaseTx are:

StatusMeaning
PENDINGRequest accepted, transaction is being processed by the acquirer. Poll /transactionState/{id} for the outcome.
ERRORThe request itself was malformed or rejected before routing. Check the errorMessage field.

For /generateTransactionId you'll see:

StatusMeaning
SUCCESSQR / code resolved. Continue to purchase.
NEED_INPUTLookup needs additional inputs (e.g. account number for a bill payment). Prompt the user, then call lookup again with inputs.
ERRORThe QR / code couldn't be resolved.

The full final-state list — END_BANK_NON_00, OFF_TO_BANK, END_REVERSED, and 80+ more — lives in Transaction states.


The four shapes of QR you'll resolve

The generateTransactionId endpoint accepts a value and figures out what kind of code it is. Four shapes are supported:

QR shapeExample valueWhen you'll see it
10-digit Scan to Pay code8656849931Static merchant QR or generated dynamic code
Short URLhttps://payat.io/qr/11669Legacy short-link QR — typically for bill payments
EMV TLV00020101...EMV-compliant QR per PASA QR standard
Bank-scheme URLmasterpass.sbsa.scheme://...App-to-app deep link from a merchant's app

Your wallet shouldn't need to distinguish these — pass them all to value and let the lookup endpoint normalise. The response is identical regardless of input shape, with value rewritten to a canonical 10-digit code your subsequent /purchaseTx call must use.


VAS (Value-Added Services)

In addition to merchant QR payments, the Remote API exposes a VAS menu — airtime top-ups, data bundles, bill payments, donations — curated for your specific wallet brand by the Scan to Pay Operations team.

Calling GET /menu returns a tree of menu items with images, input prompts, and the lookup-action references your wallet needs to drive the customer flow. Each leaf is a LOOKUP action that you submit to /generateTransactionId exactly like a scanned QR.

VAS configuration (which products show up, the menu hierarchy, the brand images) is set per-wallet by Operations — contact [email protected] to enable a new VAS product.


Webhooks vs polling

Like the Merchant API, the Remote API offers two ways to learn a transaction's final state:

  • Webhook — pass a webhook URL on the /purchaseTx request; Scan to Pay POSTs the final state to your endpoint. See Webhooks for the payload shape and acknowledgement rules.
  • Polling — call /transactionState/{transactionId} on a cadence until you see a END_* status. See Querying transactions for cadence guidance.

For wallet UIs, polling is usually the right call — the wallet's payment screen is already in the foreground, the customer is waiting, and a 1-2 second poll loop gives the best UX. Webhooks are useful when your wallet has a server-side ledger that must be updated even if the customer closes the app mid-transaction.


Error semantics

When something fails, the response body looks like:

{
  "status": "ERROR",
  "errorMessage": "Invalid card number",
  "errorCode": "INVALID_CARD"
}

The HTTP layer reports:

HTTP codeMeaning
200Request processed (check status in body — could still be a transaction-level ERROR)
400Malformed request — see errorMessage
401Bad credentials or expired JWT
500Internal server error — retry with the same transactionId to recover

For the full Scan to Pay error code list shared across the platform, see Errors.


A minimal end-to-end call

The shortest possible Remote API integration is two calls:

# 1. Look up the QR / code
curl -X POST https://qa.scantopay.io/pluto/remote/v4/generateTransactionId \
  -u 'wallet_username:wallet_password' \
  -H 'Content-Type: application/json' \
  -d '{
    "msisdn": "27832006283",
    "value": "8656849931"
  }'

# 2. Purchase with the returned transactionId
curl -X POST https://qa.scantopay.io/pluto/remote/v4/purchaseTx \
  -u 'wallet_username:wallet_password' \
  -H 'Content-Type: application/json' \
  -d '{
    "msisdn": "27832006283",
    "value": "8656849931",
    "transactionId": 469623,
    "cardNumber": "5123451234567897",
    "expiryDate": "1228",
    "amount": 5.75,
    "pinData": { "encryptedData": "<encrypted PIN block>" }
  }'

Walked through end-to-end in Purchase flow.


What's next