Card-present terminals · TMS · reporting

Handpoint API

Three Handpoint surfaces consolidated here: the REST API for taking payments on physical terminals (cloud.handpoint.com), the TMS API for provisioning merchants and terminals (tmsdoc.handpoint.com), and the Transaction Feed APIfor pulling settled transactions into your reporting (txnfeed.handpoint.io). Each surface has its own credential – they don’t cross over.

Reformatted from the public Handpoint docs. Where these docs and Handpoint’s upstream disagree, trust upstream.

Overview

Introduction

Handpoint is a P2PE-encrypted terminal cloud. Merchant software hits our REST API to tell a terminal to take a sale; the terminal runs the EMV / contactless / swipe flow and returns the result.

Three independent APIs ship under the Handpoint umbrella, each with its own auth and its own base URL:

  • REST API – taking payments on physical terminals.
  • TMS API – onboarding merchants, assigning terminals, minting API keys.
  • Transaction Feed API – pulling transactions for reporting / dashboards.
One credential per surface

Authentication

Each surface uses a different credential and a different header:

  • REST APIApiKeyCloud header. The key is unique per merchant; you mint it through the TMS API.
  • TMS API – JWT obtained from a Login endpoint, passed in Authorization: Bearer on subsequent calls.
  • Transaction Feed APIAnalyticsApiKey header. Separate key from the REST API.
↑ Request – what you send
# REST API (transactions on terminals)
ApiKeyCloud: <merchant-api-key>

# TMS API (merchant + terminal provisioning)
Authorization: Bearer <jwt-from-login>

# Transaction Feed API (reporting)
AnalyticsApiKey: <analytics-api-key>
Production vs debug

Environments

Pick the base URL by terminal type, not by where your code is running. Production / demo terminals hit cloud.handpoint.com; debug terminals hit cloud.handpoint.io. Demo terminals use a mock acquirer – no funds move, but every other behavior is real.

↑ Request – what you send
# Production (REST)        – also used for "demo" terminals (mock acquirer, no funds moved)
https://cloud.handpoint.com

# Debug / development (REST)
https://cloud.handpoint.io

# Transaction status / lookup (REST)
https://transactions.handpoint.com   # production
https://transactions.handpoint.io    # debug

# Transaction Feed
https://txnfeed.handpoint.io
Callback or polling

Flow models (push vs poll)

Every transaction request returns 202 Accepted with a transactionResultId– the terminal hasn’t actually run the transaction yet. You get the real result one of two ways:

  • Push (4.1) – supply callbackUrl + token in the request. Handpoint POSTs the result to your URL when the terminal finishes; the token echoes in AUTH-TOKEN so you can authenticate the callback. Your callback URL must accept a certificate authority Android 5-10 supports (terminal OS versions).
  • Poll (4.2) – omit those fields. GET /transaction-result/{id} with the returned id. Returns 204 while in flight, 200 with the result when done.
↑ Request – what you send
// Push (4.1) – include callbackUrl + token in the
// transaction request. Handpoint POSTs the result back to
// your callback when the terminal finishes; AUTH-TOKEN
// echoes the token you sent.

{
  "operation": "sale",
  "amount":    "10000",
  "currency":  "EUR",
  "callbackUrl": "https://your.app/handpoint-callback",
  "token":       "session-7c1d"
}

// Poll (4.2) — omit callbackUrl + token. The API returns
// transactionResultId immediately; you poll
// GET /transaction-result/{id} until you get a 200.

{
  "operation": "sale",
  "amount":    "10000",
  "currency":  "EUR"
}
01
API surface 1 of 3

REST API

Taking payments on physical terminals – sale, refund, void, pre-auth, MOTO, batch, and device controls.

cloud.handpoint.com·Auth: ApiKeyCloud header
GET /devices

List devices

Retrieves every payment terminal (and Virtual Terminal) associated with the merchant. Use the returned serial_number + terminal_type on every subsequent transaction request.

↑ Request – what you send
curl -X GET https://cloud.handpoint.com/devices \
  -H "ApiKeyCloud: <merchant-api-key>"
POST /transactions

Send transaction

Sends an operation to the terminal. Valid operation values include sale, refund, refundReversal, preAuthorizationCapture, and more.

amount is in minor units (cents) as a string: "10000" = €100.00. transactionReference must be a UUID v4 for original operations; linked operations (refund, reversal) pass originalTransactionId instead.

Common error codes: 1001 device busy, 1002 device offline / not responding, 1003 cancel operation not allowed.

Body parameters
FieldTypeDescription
operationrequiredstringTerminal operation: sale, refund, refundReversal, preAuthorizationCapture, etc.
amountrequiredstringAmount in minor units (cents) as a string. "10000" = €100.00.
currencyrequiredstringISO 4217 currency code, e.g. EUR.
terminal_typerequiredstringTerminal model from List devices, e.g. PAXA920.
serial_numberrequiredstringTerminal serial number from List devices.
transactionReferencerequiredstringUUID v4 for original operations; echoed back on the result.
customerReferenceoptionalstringYour own reference for the operation.
originalTransactionIdoptionalstringLinks a refund or reversal to the original transaction.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/transactions \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "operation": "sale",
  "amount": "10000",
  "currency": "EUR",
  "terminal_type": "PAXA920",
  "serial_number": "1547854757",
  "customerReference": "op15248",
  "transactionReference": "2bfde1fc-23b1-4c67-93d9-1d4a557f4d4f"
}'
GET /transaction-result/{id}

Fetch result (poll)

Polling endpoint for flow model 4.2. Hit it until you get a 200. Returns the full transaction-result object with masked PAN, card scheme, AVS / CVV results, signature URL, customer + merchant receipt text, and the cardToken you can re-use for MOTO calls within 12 months.

Path parameters
FieldTypeDescription
idrequiredstringThe transactionResultId returned by POST /transactions.
↑ Request – what you send
curl -X GET https://cloud.handpoint.com/transaction-result/1850025030-1776238708721 \
  -H "ApiKeyCloud: <merchant-api-key>"
GET /transactions/{ref}/status

Transaction status

Query by the transactionReferenceyou supplied on the original request. Useful when you’ve lost the transactionResultId or when you need to find every linked operation (refunds, reversals, captures) tied to one original sale – pass selector=all.

Lives on a different host: transactions.handpoint.com / .io.

finStatus values: AUTHORISED, DECLINED, UNDEFINED, IN_PROGRESS, REFUNDED, CANCELLED.

FieldTypeDescription
transactionReferencerequiredstringPath – the reference you supplied on the original request.
selectoroptionalstringQuery – which linked op to return: first (default), last, all, or a numeric index.
↑ Request – what you send
curl -X GET https://transactions.handpoint.com/transactions/{transactionReference}/status \
  -H "ApiKeyCloud: <merchant-api-key>"
POST /transactions/{guid}/tip-adjustment · US only

Tip adjustment

Adjust the tip amount on an already-authorized sale before batch settlement. US restaurants only; TSYS / VANTIV acquirers only. amount is in major units (dollars) – note the mismatch with the transaction amount field which is in minor units.

FieldTypeDescription
guidrequiredstringPath – GUID of the authorized sale to adjust.
amountrequiredstringBody – new tip amount in major units (dollars), e.g. "5.25".
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/transactions/ff6da784-8b57-11ed-9891-ebe2a88ff071/tip-adjustment \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "5.25"
}'
GET /transactions/{guid}/token

Card token (deferred tokenization)

Retrieve a card token from a completed card-present transaction so you can charge the same card via the MOTO endpoints later. Eligible transaction types: sale, refund, preAuthorizationCapture, moToSale, moToRefund. Must be requested within 12 months of the original transaction.

Path parameters
FieldTypeDescription
guidrequiredstringGUID of the completed transaction to tokenize.
↑ Request – what you send
curl -X GET https://cloud.handpoint.com/transactions/{guid}/token \
  -H "ApiKeyCloud: <merchant-api-key>"
POST /preauthorization/increase

Pre-auth increase / decrease

Modify the authorized amount on an open pre-authorization without re-running the card. increaseAmount is the delta in major units. Pass subtract: "1" to decrease.

Body parameters
FieldTypeDescription
originalGuidrequiredstringGUID of the open pre-authorization.
increaseAmountrequiredstringDelta to apply, in major units (dollars).
subtractoptionalstringSet to "1" to decrease instead of increase.
tipAmountoptionalstringOptional tip to add, in major units.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/preauthorization/increase \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a",
  "increaseAmount": "10.00"
}'
POST /preauthorization/capture

Pre-auth capture

Finalize an open pre-auth and actually charge the card. capturedAmountis in major units; can’t exceed the authorized amount (after any increases). Add a tip via tipAmount.

Body parameters
FieldTypeDescription
originalGuidrequiredstringGUID of the open pre-authorization.
capturedAmountrequiredstringAmount to capture, in major units; can't exceed the authorized amount.
tipAmountoptionalstringOptional tip to add, in major units.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/preauthorization/capture \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a",
  "capturedAmount": "120.00"
}'
POST /moto/sale

MOTO sale

Charge a previously-tokenized card without involving a terminal. cardToken comes from a prior card-present sale (or the deferred-tokenization endpoint). amount in major units.

Body parameters
FieldTypeDescription
amountrequiredstringAmount to charge, in major units (dollars).
currencyrequiredstringISO 4217 currency code.
cardTokenrequiredstringToken from a prior card-present sale or the deferred-tokenization endpoint.
transactionReferencerequiredstringUUID v4 for this operation.
customerReferenceoptionalstringYour own reference for the order.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/moto/sale \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "20.00",
  "currency": "EUR",
  "cardToken": "665630867",
  "customerReference": "order-12345",
  "transactionReference": "b7b2360d-3e9e-4b62-9a3a-2e6ef6c5cd01"
}'
POST /moto/refund

MOTO refund

Refund a prior MOTO sale (linked by originalGuid). Amount can’t exceed the original; currency must match.

Body parameters
FieldTypeDescription
originalGuidrequiredstringGUID of the MOTO sale being refunded.
amountrequiredstringRefund amount in major units; can't exceed the original.
currencyrequiredstringMust match the original transaction's currency.
transactionReferencerequiredstringUUID v4 for this operation.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/moto/refund \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "originalGuid": "1a41d9f0-cf72-11f0-95b2-770b7d1d8e67",
  "amount": "5.00",
  "currency": "EUR",
  "transactionReference": "a1fe8db5-69a4-4b4d-a704-94ac2570f9b0"
}'
POST /moto/reversal

MOTO reversal

Void a previous MOTO sale within the reversal window.

Body parameters
FieldTypeDescription
originalGuidrequiredstringGUID of the MOTO sale to void.
amountrequiredstringAmount to reverse, in major units.
currencyrequiredstringISO 4217 currency code.
transactionReferencerequiredstringUUID v4 for this operation.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/moto/reversal \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de",
  "amount": "20.00",
  "currency": "EUR",
  "transactionReference": "4d7b1a2c-5bfd-4a30-9b6f-123456789abc"
}'
Beta · acquirer-dependent

Batch close / summary / detail

Three related endpoints, all POST, all take { deviceType, serialNumber, batchNumber } in the body:

POST/batch/closeRequest batch closure.
POST/batch/summaryTotals + transaction count (same body).
POST/batch/detailArray of every transaction in the batch (filter with optional rrn).

All three are Beta and depend on the underlying acquirer supporting them.

Body parameters
FieldTypeDescription
deviceTyperequiredstringTerminal model, e.g. PAXA920MAX.
serialNumberrequiredstringTerminal serial number.
batchNumberrequiredstringBatch to act on.
rrnoptionalstring/batch/detail only: filter to a single transaction by RRN.
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/batch/close \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "deviceType": "PAXA920MAX",
  "serialNumber": "2740013262",
  "batchNumber": "1"
}'
Six POST endpoints

Device controls

Six administrative controls for a terminal – every endpoint returns 202 Accepted when the command is queued. Common error: 400 DeviceNotListening means the terminal isn’t connected right now.

POST/devices/{deviceType}/{serialNumber}/set-unattended-modeHide navigation, show payment only. Body { status: true|false }.
POST/devices/{deviceType}/{serialNumber}/set-localeSet locale (IETF BCP 47 tag). Body { locale: "en_CA" }.
POST/devices/{deviceType}/{serialNumber}/set-password-protectedToggle password protection. Body { status: true|false }.
POST/devices/{deviceType}/{serialNumber}/rebootReboot; force: true reboots mid-transaction. Body { force: false }.
POST/devices/{deviceType}/{serialNumber}/set-screen-brightnessScreen brightness range 0-100. Body { minimumBrightnessLevel, maximumBrightnessLevel }.
POST/devices/{deviceType}/{serialNumber}/set-reboot-timeDaily auto-reboot hour 0-23 (production only). Body { hour: 22 }.
FieldTypeDescription
deviceTyperequiredstringPath – terminal model.
serialNumberrequiredstringPath – terminal serial number.
statusoptionalbooleanBody – set-unattended-mode / set-password-protected: enable or disable.
localeoptionalstringBody – set-locale: IETF BCP 47 tag, e.g. en_CA.
forceoptionalbooleanBody – reboot: true reboots mid-transaction.
minimumBrightnessLeveloptionalintegerBody – set-screen-brightness: low end of range, 0-100.
maximumBrightnessLeveloptionalintegerBody – set-screen-brightness: high end of range, 0-100.
houroptionalintegerBody – set-reboot-time: daily auto-reboot hour, 0-23 (production only).
↑ Request – what you send
curl -X POST https://cloud.handpoint.com/devices/{deviceType}/{serialNumber}/set-unattended-mode \
  -H "ApiKeyCloud: <merchant-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "status": true
}'
02
API surface 2 of 3

TMS API

Terminal Management System – create merchants, assign terminals, mint API keys for the REST surface. Provisioning only; no payments cross this API.

tmsdoc.handpoint.com·Auth: Bearer JWT from POST /login
JWT auth

Login

Login returns a JSON Web Token. Send it in the Authorization: Bearer … header on every other TMS call. Tokens have a fixed lifetime – handle re-login when calls return 401.

↑ Request – what you send
# 1. Login (credentials → JWT)
POST /login

# 2. Every subsequent call:
Authorization: Bearer <jwt>
Create · read · update · delete

Merchants

Merchant lifecycle. The POST /merchants body must match your partner configuration (ISV id, acquirer id, etc.) – ask your Handpoint contact for the exact shape.

GET /merchants/{id}returns the merchant’s shared secret and other auth-grade fields; treat the response like a credential. After PUT, re-publish each assigned terminal’s configuration (see Lifecycle below).

↑ Request – what you send
POST   /merchants               create
GET    /merchants               list all under the partner
GET    /merchants/{id}          retrieve profile (includes shared secret)
PUT    /merchants/{id}          update – re-publish terminals after
DELETE /merchants/{id}          must have zero assigned terminals
Create · assign · publish · unassign · delete

Terminals

Each terminal lives in one of two states: unassigned (in the pool, no merchant) or assigned (attached to exactly one merchant). Reassignment is unassign → assign.

You must call POST /publish after every assign / unassign / merchant-edit.Without it the Handpoint Gateway doesn’t know about the change and the terminal will keep authorizing under the old merchant context.

↑ Request – what you send
POST   /terminals                          add to unassigned pool
GET    /terminals/unassigned               list pool (searchable)
POST   /terminals/{id}/assign              attach to a merchant
POST   /terminals/{id}/publish             push config to Gateway
                                             (REQUIRED after assign / change)
POST   /terminals/{id}/unassign            back to the pool
GET    /merchants/{m}/terminals            list assigned (searchable)
DELETE /terminals/{id}                     terminal must be unassigned first
Mint REST API + Txn Feed keys

API keys

Cloud API keys are minted per merchant from the TMS. The POSTresponse includes the unmasked key – that’s the only time you’ll see it. Store it server-side before handing to the merchant.

↑ Request – what you send
POST   /merchants/{id}/api-keys           mint a new Cloud API key
GET    /merchants/{id}/api-keys           list (masked)
DELETE /merchants/{id}/api-keys/{key}     revoke
Standard onboarding

Lifecycle workflow

Standard onboarding for a new merchant + first terminal is six API calls in sequence:

  • Login → JWT.
  • Create the merchant.
  • Add the terminal to the unassigned pool.
  • Assign the terminal to the merchant.
  • Publish the terminal config to the Gateway.
  • Mint the merchant’s REST API key.
↑ Request – what you send
# 1. Login                                 → JWT
# 2. POST   /merchants                     → create
# 3. POST   /terminals                     → add terminal to pool
# 4. POST   /terminals/{id}/assign         → attach to merchant
# 5. POST   /terminals/{id}/publish        → tell the Gateway
# 6. POST   /merchants/{id}/api-keys       → mint REST key for the merchant

# Ongoing – after ANY merchant edit or terminal assignment change:
#   re-publish each affected terminal.
03
API surface 3 of 3

Transaction Feed API

Read-only reporting side – pull transactions, run aggregates, build dashboards. Separate analytics key from the REST API; cannot take payments. API version 2.8.0.

txnfeed.handpoint.io·Auth: AnalyticsApiKey header
Header-based

Authentication

Different credential from the REST API. The Transaction Feed is a read-only reporting surface; the key here doesn’t let you take payments. Ask Handpoint to provision a separate Analytics key – don’t reuse the Cloud API key.

↑ Request – what you send
curl -X POST https://txnfeed.handpoint.io/transactions \
  -H "AnalyticsApiKey: Your-Api-Key" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "startDate": "20260101000000",
  "endDate": "20260201000000"
}'
Date-range filter

Request config

Date format is a single string of digits: YYYYMMDDhhmmss. 20260609143200 = June 9, 2026 at 14:32:00. Both startDate and endDate are required on every POST.

Body parameters
FieldTypeDescription
startDaterequiredstringWindow start, YYYYMMDDhhmmss (14 digits, 24-hour, no separators).
endDaterequiredstringWindow end, same format.
↑ Request – what you send
// Every POST takes a RequestConfig body
{
  "startDate": "20260101000000",
  "endDate":   "20260201000000"
}

// Format: YYYYMMDDhhmmss (14 chars, 24-hour, no separators).
Five flavors

Transaction lookups

Five basic lookups, two type-filters (payouts = income, refundsandreversals = outflow), plus admin variants scoped to a specific merchant or to the partner level.

Every endpoint is POST so the body can carry the date range. The path parameters (guid, customerReference, partnerIdAlpha, merchantIdAlpha) sit in the URL.

FieldTypeDescription
guidoptionalstringPath – single-transaction endpoints: the transaction GUID.
customerReferenceoptionalstringPath – by-reference endpoints: your own reference.
partnerIdAlphaoptionalstringPath – admin endpoints: partner scope.
merchantIdAlphaoptionalstringPath – admin endpoints: merchant scope.
guidsoptionalarrayBody – /transactions/guids: bulk list of GUIDs.
customerReferencesoptionalarrayBody – /transactions/customerreferences: bulk list of references.
↑ Request – what you send
POST /transactions                                                # all transactions in window
POST /transactions/{guid}                                         # one by guid
POST /transactions/guids                                          # bulk by guid (body)
POST /transactions/customerreference/{customerReference}          # by your reference
POST /transactions/customerreferences                             # bulk by your reference

# Pre-filtered by direction:
POST /transactions/payouts                                        # sales + captures
POST /transactions/refundsandreversals                            # refunds + reversals

# Admin endpoints (require partner + merchant ids):
POST /transactions/{partnerIdAlpha}/{merchantIdAlpha}
POST /transactions/{partnerIdAlpha}/{merchantIdAlpha}/payouts
POST /transactions/{partnerIdAlpha}/{merchantIdAlpha}/refundsandreversals

# Partner-or-merchant context:
POST /transactions/partnerormerchant
POST /transactions/partnerormerchant/payouts
POST /transactions/partnerormerchant/refundsandreversals
Avg · Sum · Count · Distinct count · Net

Aggregations

Five aggregation families. {field}is the JSON attribute you’re aggregating over (amount, serialNumber, cardType, etc.). Net is a built-in convenience: payouts minus refunds+reversals.

Path parameters
FieldTypeDescription
fieldrequiredstringJSON attribute to aggregate over, e.g. amount, serialNumber, cardType. (Count endpoints omit it.)
↑ Request – what you send
# Avg
POST /transactions/aggregate/avg/{field}
POST /transactions/aggregate/avg/payouts/{field}
POST /transactions/aggregate/avg/refundsandreversals/{field}

# Sum
POST /transactions/aggregate/sum/{field}
POST /transactions/aggregate/sum/payouts/{field}
POST /transactions/aggregate/sum/refundsandreversals/{field}

# Count
POST /transactions/count
POST /transactions/count/payouts
POST /transactions/count/refundsandreversals

# Distinct count
POST /transactions/aggregate/distinctcount/{field}

# Net (payouts − refundsandreversals)
POST /transactions/aggregate/net/{field}
groupBy + calendarInterval

Grouping + time series

Add /group/ to the path and supply three query params:

FieldTypeDescription
oprequiredstringPath – aggregation op: avg, sum, count, distinctcount, or net.
fieldrequiredstringPath – JSON attribute to aggregate over (omit for count).
groupByrequiredstringQuery – comma-separated field names. Multiple keys = multiple group dimensions.
calendarIntervaloptionalstringQuery – time bucket size, e.g. HOUR, DAY, MONTH.
orderoptionalstringQuery – ASC or DESC.
↑ Request – what you send
# Generic grouped aggregate (works for avg/sum/count/distinctcount/net)
POST /transactions/aggregate/group/{op}/{field}?groupBy=serialNumber,cardType&calendarInterval=DAY&order=DESC

# Variants:
/aggregate/group/avg/{field}
/aggregate/group/avg/payouts/{field}
/aggregate/group/avg/refundsandreversals/{field}
/aggregate/group/sum/{field}
/aggregate/group/count
/aggregate/group/distinctcount/{field}
/aggregate/group/net/{field}
Visualization-ready

Graph endpoints

Same shape as /group/but the response is pre-bucketed for charting libraries (timestamps on the X axis, the aggregate on Y). Skip the per-row mapping you’d normally do for a graph.

FieldTypeDescription
fieldrequiredstringPath – JSON attribute to aggregate over (omit for count).
groupByrequiredstringQuery – comma-separated field names.
calendarIntervaloptionalstringQuery – time bucket size, e.g. DAY.
orderoptionalstringQuery – ASC or DESC.
↑ Request – what you send
POST /transactions/aggregate/group/graph/avg/{field}
POST /transactions/aggregate/group/graph/sum/{field}
POST /transactions/aggregate/group/graph/count
POST /transactions/aggregate/group/graph/distinctcount/{field}
POST /transactions/aggregate/group/graph/net/{field}

# Same groupBy / calendarInterval / order params as the
# non-graph variants. Response shape is pre-bucketed for
# direct charting.
Common status codes

Errors

StatusMeaning
200OK
202Accepted – operation queued, result via callback or polling.
204No content – used by the polling result endpoint while in-flight.
400Bad request. REST error codes: 1001 busy, 1002 offline, 1003 cancel not allowed, 3107 CVV required, 3209 amount exceeds original, 3210 currency mismatch, 3211 pre-auth closed, 5252 token failure.
403Authentication failed or insufficient permissions.
404Resource not found (transactionResultId, transactionReference, etc.).
422Unprocessable – missing required fields.
5xxInternal Handpoint error.