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.
| Capability | Operation | Endpoint |
|---|---|---|
| Lookup the QR / value | Resolve a scanned code into a merchant, amount, accepted cards, and auth method | POST /generateTransactionId |
| Pay | Submit card + auth data → acquirer | POST /purchaseTx |
| Initiate 3DS authentication if the card requires it | POST /secureCodeLookup | |
| Track the result | Poll for the final state | POST /transactionState/{transactionId} |
| Show last 10 transactions in the wallet UI | POST /transactionHistory | |
| Help the UI | Look up card requirements by BIN (CVV needed? expiry needed?) | POST /binLookup |
| Get the VAS menu (airtime, bills, donations) for your wallet | GET /menu |
Base URLs
| Environment | Base URL |
|---|---|
| Sandbox / QA | https://qa.scantopay.io/pluto/remote/v4 |
| Production | https://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).
| Mode | Header | When to use |
|---|---|---|
| Basic Auth | Authorization: Basic <base64(username:password)> | Historical default. Still supported for existing integrations. |
| JWT Bearer + PKI | Authorization: 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.
| Field | Used on | Description |
|---|---|---|
msisdn | Almost every request | Wallet owner's mobile number in international format (e.g. 27832006283). The wallet's primary identifier. |
value | Lookup, Purchase | The QR / 10-digit code / URL that was scanned. URL-encoded if you're passing it on a query string. |
transactionId | Purchase, State, SecureCode | Long integer returned by generateTransactionId. Carries the merchant + amount + auth context through the flow. |
cardNumber, expiryDate, account, cvv2 | Purchase | Card data your wallet vaulted at provisioning time. |
pinData or secureCodeData | Purchase | Either an encrypted PIN block (AMT) or 3D Secure verification fields. Required, mutually exclusive. |
deviceData | Lookup, Purchase | Optional 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:
| Status | Meaning |
|---|---|
PENDING | Request accepted, transaction is being processed by the acquirer. Poll /transactionState/{id} for the outcome. |
ERROR | The request itself was malformed or rejected before routing. Check the errorMessage field. |
For /generateTransactionId you'll see:
| Status | Meaning |
|---|---|
SUCCESS | QR / code resolved. Continue to purchase. |
NEED_INPUT | Lookup needs additional inputs (e.g. account number for a bill payment). Prompt the user, then call lookup again with inputs. |
ERROR | The 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 shape | Example value | When you'll see it |
|---|---|---|
| 10-digit Scan to Pay code | 8656849931 | Static merchant QR or generated dynamic code |
| Short URL | https://payat.io/qr/11669 | Legacy short-link QR — typically for bill payments |
| EMV TLV | 00020101... | EMV-compliant QR per PASA QR standard |
| Bank-scheme URL | masterpass.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
webhookURL on the/purchaseTxrequest; 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 aEND_*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 code | Meaning |
|---|---|
200 | Request processed (check status in body — could still be a transaction-level ERROR) |
400 | Malformed request — see errorMessage |
401 | Bad credentials or expired JWT |
500 | Internal 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
- Step through a full wallet purchase → Purchase flow
- Integration rules and constraints → Remote API — business rules
- Link a card to a wallet profile → Card Provisioning
- Decode every possible transaction state → Transaction states
- Authentication walk-through → Authentication
Updated 2 days ago
