Initiating a test transaction over REST
REST is the simplest way to initiate a test: one HTTPS call, authenticated with an API key — the call itself uses no mTLS. Completing the transaction is a separate matter: the outbound leg to your fintech backend rides an mTLS TCP connection that your backend must already hold open as the responder, so a full round trip also needs a client certificate and a live connection.
Complete Before you begin first. For a structured
transaction call you need an sk_live_… API key; a configured behaviour for the
card is optional and only shapes the simulated outcome — without one the engine
returns its normal faithful response. To exercise the full round trip you also
need your fintech backend connected over mTLS as the responder — see
Delivery needs a live TCP connection
below; without it the call returns 502.
This page is the narrative walkthrough. For the machine-readable contract —
request/response schemas and every error body, for every operation and channel —
see the API reference, generated from src/openapi/switchbench.yaml.
Keep the two in sync when either changes.
Structured transactions
Section titled “Structured transactions”Switchbench’s REST transaction API is a family of routes, one per operation and
acceptance channel — for example POST /transaction/sale/pos or
POST /transaction/reversal/ecom. You send typed JSON fields and Switchbench
builds the ISO 8583 wire message for you; there is no hand-packed hex to
assemble. Every route shares the same authentication, rate limiting, and RFC
7807 error shape; only the request/response bodies differ. Visa only
today — a mastercard request returns 501 Not Implemented until Mastercard
structured support lands.
POST https://api.switchbench.com/transaction/sale/posNon-production environments use a single-level environment prefix on the host —
for example https://test-api.switchbench.com/transaction/sale/pos. Every route
is fronted by nginx behind a Cloudflare Tunnel; the path and payload are
identical across environments.
Every route from the tables below is documented individually, with its own request/response schema and example, in the API reference under Transaction.
Authentication
Section titled “Authentication”Send your API key as a bearer token:
Authorization: Bearer sk_live_...JWT bearer tokens are rejected with 401 on every structured transaction
route. A CPS-validated API key is required. A Logto-issued JWT — even a valid
one carrying an organization_id claim — is refused here, because SMP cannot
yet map a Logto org to the numeric org_id server-side without trusting a
caller-supplied id. Use the API key.
The API key is a Control Plane Service (CPS) product credential, not an OIDC token.
Capability matrix
Section titled “Capability matrix”Only these operation × channel combinations are registered as routes. Any
other combination — for example sale/atm or cash-withdrawal/ecom — is a
plain 404, not a validation error, because the route was never registered:
| Operation | pos | contactless | magstripe | ecom | ecom-cof | moto | atm |
|---|---|---|---|---|---|---|---|
sale | X | X | X | X | X | X | - |
advice | X | X | X | X | X | X | X |
cash-withdrawal | X | X | - | - | - | - | X |
balance-inquiry | X | X | - | - | - | - | X |
refund | X | X | X | X | X | X | - |
reversal | X | X | X | X | X | X | - |
void | X | X | X | X | X | X | - |
Three routes have no {channel} path segment because the channel is fixed:
| Route | Notes |
|---|---|
POST /transaction/account-verification | A zero-amount sale; always the e-commerce use case internally. |
POST /transaction/pin/change | Always ATM. |
POST /transaction/pin/unblock | Always ATM. |
POST /transaction/pin/check/{channel} is card-present/ATM only — pos,
contactless, or atm.
POST /transaction/network-management/{action} takes one of echo,
sign-on, sign-off, key-exchange as {action}; it is a control message
with no channel, card, or amount.
Request fields
Section titled “Request fields”The financial and PIN routes share one request body. The route carries the operation and channel, so the body does not:
| Field | Required | Notes |
|---|---|---|
network | no | Target network. Defaults to visa, the only network supported today; any other value (including mastercard) returns 501. A network whose product the organization is not entitled to returns 403. |
envelope | no | Acceptance context: currency (DE 49, ISO 4217 alphabetic or numeric), merchant_id (DE 42), and terminal_id (DE 41). Each field you leave out is filled from your merchant/terminal/currency catalog. |
amount.amount | depends on the route | Transaction amount in minor units (DE 4). Required for sale, refund, cash-withdrawal, reversal, void, and advice (except a balance-inquiry advice). Ignored for balance-inquiry, account-verification (both sent as amount 0), and the PIN routes. |
amount.fee | no | Transaction fee in minor units (DE 28). Only sale sends it; every other route ignores it, including account-verification. |
card | yes | See Card fields. |
original_ref | no | Reference to an earlier message, for reversal, advice, and void. refund ignores it, so a refund is not linked to the original sale. See the reversal/ecom example. |
type | no | Advice sub-type for the advice routes: sale, cash-withdrawal, balance-inquiry, or refund. Ignored by every other route. |
new_pin | pin/change only | The new clear PIN digits. Required for POST /transaction/pin/change; ignored by every other route. |
POST /transaction/network-management/{action} takes a smaller body:
network as above, plus an optional type — the DE 70 network-management
code for key-exchange (3 or 4 digits, for example 101; defaults to the
key-change-request code). type is ignored for echo, sign-on, and
sign-off.
The API reference has the full schema for both bodies.
Card fields
Section titled “Card fields”The card object is the same shape for every channel — there is no
channel-specific variant on the public API today:
| Field | Required | Notes |
|---|---|---|
pan | yes | Primary account number (DE 2). |
expiry | yes | Card expiry as MMYY (DE 14). |
cvv2 | no | Card verification value 2. |
cardholder_name | no | For the card acceptor record. |
pin | no | Clear PIN digits. Only consumed by pin-check, pin-change (the current PIN), and pin-unblock; ignored by every other operation. |
There is no field for track 2 data, EMV/ICC data, or an encrypted PIN block
on any channel, including pos/contactless/magstripe/atm — SMP binds
card as this one shape regardless of channel, so those fields would be
silently discarded if sent.
Worked example: sale/pos
Section titled “Worked example: sale/pos”A USD 10.00 sale on a contact-chip POS terminal:
curl -X POST https://test-api.switchbench.com/transaction/sale/pos \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "amount": { "amount": 1000 }, "card": { "pan": "4111111111111111", "expiry": "1229", "cvv2": "123" } }'Response shape on success (200 OK) — a declined transaction
(parsed_response’s DE 39 ≠ "00") is still 200, not an HTTP error:
{ "request_id": "req_abc123", "sent": "0200B23A80010000000000004111111111111111000000001000", "parsed_request": { "mti": "0200", "fields": { "02 (Primary Account Number)": "4111111111111111", "03 (Processing Code)": "000000", "04 (Amount, Transaction)": "000000001000" } }, "last_response_hex": "0210B23A80010000000000004111111111111111000000001000C1B2C300", "parsed_response": { "mti": "0210", "fields": { "02 (Primary Account Number)": "4111111111111111", "38 (Authorization Identification Response)": "C1B2C3", "39 (Response Code)": "00" } }}Worked example: reversal/ecom
Section titled “Worked example: reversal/ecom”A reversal of a prior e-commerce sale, referencing the original transaction by
its DE 11 (STAN), DE 37 (RRN), and DE 38 (approval code) via original_ref:
curl -X POST https://test-api.switchbench.com/transaction/reversal/ecom \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "amount": { "amount": 1000 }, "card": { "pan": "4111111111111111", "expiry": "1229", "cvv2": "123" }, "original_ref": { "original_stan": "000123", "original_rrn": "123456789012", "original_auth_code": "C1B2C3" } }'{ "request_id": "req_def456", "sent": "0420323A800100000000000041111111111111110000000010000000000123123456789012", "parsed_request": { "mti": "0420", "fields": { "02 (Primary Account Number)": "4111111111111111", "04 (Amount, Transaction)": "000000001000", "90 (Original Data Elements)": "0100000123" } }, "last_response_hex": "0430323A800100000000000041111111111111110000000010000000000123123456789012C1B2C300", "parsed_response": { "mti": "0430", "fields": { "02 (Primary Account Number)": "4111111111111111", "38 (Authorization Identification Response)": "C1B2C3", "39 (Response Code)": "00" } }}Errors
Section titled “Errors”Errors use RFC 7807 application/problem+json.
| Status | Meaning |
|---|---|
400 | Malformed request — the body is not valid JSON or does not match the schema, or card is missing or has an empty pan. |
401 | Authentication failed — missing, invalid, revoked, or suspended API key, or a JWT was sent. |
404 | The operation × channel combination is not in the capability matrix and was never registered as a route. |
403 | The organization is not entitled to the product for the requested network — no active trial or subscription. Checked after the 501 network check, and again per frame at delivery. |
429 | Rate limit exceeded — the API key’s requests-per-second limit, or the trial’s per-product requests-per-second cap (type: trial_rate_limited). A Retry-After header is returned. |
501 | Unsupported network — only visa is supported today. |
502 | Delivery error — no active mTLS connection for the org, or a write/read failure (a delivery timeout included) on the outbound leg to your fintech backend; also an engine HTTP error or an undecodable engine response. Engine-side input validation failures (for example a missing amount or PIN) also surface here as Engine error, not 400. |
503 | API-key validation with CPS is unavailable, so the tenant could not be resolved; or the entitlement check at delivery could not be confirmed (type: entitlement_check_unavailable). Retry the request. |
504 | The Visa engine call itself timed out, hit an open circuit breaker, or failed at the transport level. |
Delivery needs a live TCP connection
Section titled “Delivery needs a live TCP connection”A structured transaction call only originates the authorization. The outbound leg to your fintech backend is written over the mTLS TCP connection your backend established through the FRP edge, acting as the responder. That connection is a separate concern from the API key: it needs a client certificate and must be open at the moment you call the route.
If no live connection is registered for your org when the call arrives, SMP has
nowhere to deliver the outbound leg: the call fails with 502 (delivery
error) and the authorization is never completed. A pure-REST caller that has
set up only an API key hits exactly this.
To exercise the full round trip, open a live mTLS TCP connection first — see Visa ISO 8583 over mTLS TCP for the connection and wire contract — then send your transaction request.
- The API key authenticates the call — nothing more.
- Completing the transaction needs your fintech backend connected over mTLS as the responder: a client certificate and a live TCP connection.
- No live connection for the org →
502delivery error, before any authorization is finalized. - Typed JSON fields, not hand-packed ISO 8583 hex — one route per operation and
acceptance channel, e.g.
POST /transaction/sale/posorPOST /transaction/reversal/ecom.