Cloud API — Operations Reference
Complete request/response examples for every Cloud API operation. Each section shows the full two-step flow — the curl command, the 202 acceptance, the poll, and the final result — alongside a parameter table and the most common error responses.
For authentication, environments, and callback vs polling delivery, see the Cloud API Integration Guide.
For error code definitions and error shapes, see Error codes.
Response pattern
All terminal operations follow a two-step model. Step 1 is synchronous; step 2 is polled.
Step 1 — send the operation
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "operation": "sale", "amount": "15012", ... }'
// HTTP 202 — accepted immediately; terminal begins processing
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700677769",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}
Step 2 — poll for the result
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700677769 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — final result; finStatus other than IN_PROGRESS means complete
{
"finStatus": "AUTHORISED",
...
}
Poll at ~3s intervals until finStatus is not IN_PROGRESS. The terminal can take up to 6 minutes for a full fallback path (contactless fail → chip fail → swipe → wrong PINs).
Exception — gateway-synchronous: POST /reversal and POST /preauthorization/capture return their result directly (no poll).
Sale
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "sale",
"amount": "15012",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}'
// HTTP 202
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700677769",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}
# Step 2 — poll (use transactionResultId from step 1)
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700677769 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"tenderType": "CREDIT",
"cardEntryType": "ICC",
"paymentScenario": "CHIP",
"requestedAmount": 15012,
"totalAmount": 15012,
"currency": "USD",
"rrn": "0000906190164",
"issuerResponseCode": "00",
"arc": "0000",
"type": "SALE",
"customerReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/customer.html",
"merchantReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/merchant.html"
}
Request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
operation | string | Yes | — | "sale" |
serial_number | string | Yes | — | Terminal serial number |
terminal_type | string | Yes | — | PAX model — e.g. "PAXA920PRO". Valid values |
amount | string | Yes | — | Minor-unit amount as a string — "1000" = $10.00. Digits only, max 12 characters. |
currency | string | Yes | — | ISO 4217 alpha-3 — "USD", "GBP", "EUR" |
transactionReference | string | Recommended | auto-UUID | UUID v4. Persist before sending — required for recovery via GET /transactions/{ref}/status. Only honoured for sale, refund, saleAndTokenizeCard, preAuthorization. |
callbackUrl | string | No | — | HTTPS endpoint. Result POSTed here when complete. |
token | string | No | — | Sent as auth-token header on the callback POST. Use a unique value per request. |
customerReference | string | No | — | Free-text order reference stored with the transaction. |
duplicate_check | boolean | No | true | When true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal screen — the merchant must accept or decline. Accept → a new authorisation request is sent to the gateway; the final result is delivered normally. Decline → finStatus: CANCELLED is delivered immediately; no card charge. Set false only when you are intentionally replaying a reference (e.g. after recovering an UNDEFINED result and confirming the transaction was not charged). |
bypassOptions | object | No | — | { "signatureBypass": bool, "pinBypass": bool } — see bypassOptions |
tipConfiguration | object | No | — | Tip selection screen — see tipConfiguration |
merchantAuth | array | No | — | Multi-MID credential override — see merchantAuth |
metadata | object | No | — | { "metadata1": "…", …, "metadata5": "…" } — max 250 chars each |
moneyRemittanceOptions | object | No | — | EmerchantPay only — see moneyRemittanceOptions |
billing | object | No | — | AVS — { "zipCode": string (required), "address": string (optional) } |
taxInformation | object | No | — | Level 2 — { "taxAmount": string, "taxExempt": boolean }. taxAmount in minor units, like amount. Must be sent with purchaseOrderNumber. taxAmount is required: "0" when taxExempt is true, greater than "0" otherwise, and never above amount. See Level 2 Purchasing Data. |
purchaseOrderNumber | string | No | — | Level 2 purchase order number. Must be sent with taxInformation. Alphanumeric, max 25 chars. |
Sale and Tokenize
Simultaneously runs a sale and tokenizes the card for future MOTO/back-office use. Requires a card token provider configured on the merchant.
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "saleAndTokenizeCard",
"amount": "1215",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "b777a62d-9bfb-4b6a-b188-7f842a930770"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700034700 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"cardToken": "K33f40000000000093",
"transactionID": "e3303350-a9f3-11f1-a943-f9c9f04151d9",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"tenderType": "CREDIT",
"cardEntryType": "ICC",
"paymentScenario": "CHIPCONTACTLESS",
"requestedAmount": 1215,
"totalAmount": 1215,
"currency": "USD",
"type": "SALE"
}
The cardToken value (e.g. "K33f40000000000093") is the Cygma token format. Store it for subsequent MOTO operations.
Request parameters — same as Sale except:
| Parameter | Type | Required | Notes |
|---|---|---|---|
operation | string | Yes | "saleAndTokenizeCard" |
tipConfiguration | — | — | Not valid for saleAndTokenizeCard — omit it |
All other Sale parameters apply. tipConfiguration is not valid for saleAndTokenizeCard — omit it.
Error — token provider not configured
// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Card token failure",
"errorMessage": "",
"cardToken": "",
"transactionID": "",
"requestedAmount": 0,
"type": "SALE"
}
| Detection signal | Value |
|---|---|
finStatus | DECLINED |
transactionID | Empty string — acquirer was never contacted |
requestedAmount | 0 — no amount was processed |
cardToken | Empty string |
Tokenize Card
No-charge card tokenization. The card is presented at the terminal, tokenized, and no payment is taken. The token is returned in cardToken in the poll result. finStatus is PROCESSED on success.
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "tokenizeCard",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030"
}'
// HTTP 202
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700678000"
}
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700678000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — PROCESSED
{
"finStatus": "PROCESSED",
"statusMessage": "Approved or completed successfully",
"cardToken": "K33f40000000000093",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"cardEntryType": "ICC",
"type": "TOKENIZE_CARD"
}
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | "tokenizeCard" |
serial_number | string | Yes | Terminal serial number |
terminal_type | string | Yes | PAX model — valid values |
customerReference | string | No | Free-text reference echoed in the result |
callbackUrl | string | No | HTTPS webhook endpoint |
token | string | No | Callback auth token |
Amount and currency are not required — no charge is made. transactionReference is not honoured for tokenizeCard.
Pre-Authorization
Places a hold on funds without capturing them. Requires preAuthAllowed = true on the merchant.
# Step 1 — create the hold
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "preAuthorization",
"amount": "10000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "e4826edd-7579-48ca-9553-743e5b76d855"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700629963 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED (hold placed, no funds captured)
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"requestedAmount": 10000,
"totalAmount": 10000,
"type": "PRE_AUTHORIZATION"
}
Pre-Auth create — request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
operation | string | Yes | — | "preAuthorization" |
serial_number | string | Yes | — | Terminal serial number |
terminal_type | string | Yes | — | PAX model — valid values |
amount | string | Yes | — | Minor-unit string — "10000" = $100.00. Digits only. |
currency | string | Yes | — | ISO 4217 |
transactionReference | string | Recommended | auto-UUID | UUID v4. Links all subsequent operations via /status/all. |
callbackUrl | string | No | — | HTTPS webhook endpoint |
token | string | No | — | Callback auth token |
customerReference | string | No | — | Free-text reference |
duplicate_check | boolean | No | true | When true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal — accept sends a new authorisation, decline delivers finStatus: CANCELLED. Set false only when intentionally replaying a reference after an UNDEFINED recovery. |
bypassOptions | object | No | — | { "signatureBypass": bool, "pinBypass": bool } |
merchantAuth | array | No | — | Multi-MID override — see merchantAuth |
metadata | object | No | — | Up to 5 string fields, max 250 chars each |
# Capture — use transactionID from the hold result as originalGuid
curl -X POST https://cloud.handpoint.com/preauthorization/capture \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"capturedAmount": "10000",
"currency": "USD"
}'
// HTTP 200 — AUTHORISED (funds captured)
{
"finStatus": "AUTHORISED",
"type": "PRE_AUTHORIZATION_CAPTURE"
}
POST /preauthorization/capture — request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
originalGuid | string | Yes | — | transactionID from the Pre-Authorization create result. Max 64 chars. |
capturedAmount | string | Yes | — | Amount to capture: at most 12 digits, or 13 characters with a decimal point, no leading zeros except a single 0 before the decimal point, as in 0.50, otherwise 400 Invalid capturedAmount, must be in the major unit of currency (20.50 is 20.50 EUR). ⚠️ See unit note below. |
tipAmount | string | No | — | Optional tip to add to the capture. Same unit as capturedAmount. Max 32 chars. |
customerReference | string | No | — | Free-text reference. Max 64 chars. |
taxInformation | object | No | — | Level 2 — { "taxAmount": string, "taxExempt": boolean }. taxAmount in decimal major units, like capturedAmount. Must be sent with purchaseOrderNumber. taxAmount is required: "0" when taxExempt is true, greater than "0" otherwise, and never above capturedAmount. See Level 2 Purchasing Data. |
purchaseOrderNumber | string | No | — | Level 2 purchase order number. Must be sent with taxInformation. Alphanumeric, max 25 chars. |
capturedAmount uses major units (decimal)Pass capturedAmount as a major-unit decimal string — e.g. "45.00" for $45.00, "120.00" for $120.00. This matches increaseAmount and the /reversal amount field. It is not minor units.
POST /preauthorization/increase — request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
originalGuid | string | Yes | — | transactionID from the Pre-Authorization create result. Max 64 chars. |
increaseAmount | string | Yes | — | Delta to add (or subtract). Always a positive value. Major-unit decimal — e.g. "5.00" = $5.00. Max 32 chars. |
subtract | string | No | — | Pass "1" to decrease the hold instead of increase. "1" is the only accepted value. |
tipAmount | string | No | — | Tip amount adjustment. Same unit as increaseAmount. Max 32 chars. |
customerReference | string | No | — | Free-text reference. Max 64 chars. |
Error — pre-auth not enabled
// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Pre-authorizations are not enabled for this terminal",
"errorMessage": "",
"arc": "1000",
"cardEntryType": "ICC",
"type": "PRE_AUTHORIZATION",
"transactionID": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"issuerResponseCode": "00"
}
Error — capture exceeds hold amount
curl -X POST https://cloud.handpoint.com/preauthorization/capture \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"capturedAmount": "15000",
"currency": "USD"
}'
// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Capture amount cannot be greater than pre-auth hold amount",
"code": "3215",
"details": { "errorCode": "3215", "httpStatus": 403 }
}
}
Error — pre-auth already captured or reversed
// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Authorization has already been completed",
"code": "3052",
"details": { "errorCode": "3052", "httpStatus": 409 }
}
}
Pre-Authorization Reversal (on terminal)
Releases a pre-authorization hold without capturing it. The terminal must be connected — the card may need to be re-presented depending on the acquirer.
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "preAuthorizationReversal",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700630100 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED (hold released)
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "4f9d2200-a9f5-11f1-99ee-c974d92ef76f",
"type": "PRE_AUTHORIZATION_REVERSAL"
}
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | "preAuthorizationReversal" |
serial_number | string | Yes | Terminal serial number |
terminal_type | string | Yes | PAX model — valid values |
originalTransactionId | string | Yes | transactionID from the original pre-authorization result |
customerReference | string | No | Free-text reference |
To release a pre-auth hold remotely (without a terminal), use POST /reversal with the originalGuid set to the pre-auth transactionID. See Reversal.
Refund
Card-present refund. Requires refundAllowed = true on the merchant. For linked refunds, include originalTransactionId.
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refund",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "eb6a9a5e-ad43-4014-a8a0-b0b7243169d5"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700667457 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9",
"requestedAmount": 5000,
"totalAmount": 5000,
"type": "REFUND"
}
Request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
operation | string | Yes | — | "refund" |
serial_number | string | Yes | — | Terminal serial number |
terminal_type | string | Yes | — | PAX model — valid values |
amount | string | Yes | — | Minor-unit string — "5000" = $50.00. Digits only. |
currency | string | Yes | — | ISO 4217 |
transactionReference | string | Recommended | auto-UUID | UUID v4. Persist before sending. |
originalTransactionId | string | No | — | transactionID from the original sale. Include to link the refund (same-card enforcement). |
callbackUrl | string | No | — | HTTPS webhook endpoint |
token | string | No | — | Callback auth token |
customerReference | string | No | — | Free-text reference |
duplicate_check | boolean | No | true | When true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal — accept sends a new authorisation, decline delivers finStatus: CANCELLED. Set false only when intentionally replaying a reference after an UNDEFINED recovery. |
bypassOptions | object | No | — | { "signatureBypass": bool, "pinBypass": bool } |
merchantAuth | array | No | — | Multi-MID override |
metadata | object | No | — | Up to 5 string fields, max 250 chars each |
Linked refund — add originalTransactionId to refund against a specific sale and require the same card:
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refund",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"transactionReference": "eb6a9a5e-ad43-4014-a8a0-b0b7243169d5"
}'
Error — refunds not enabled
// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Refund not allowed",
"errorMessage": "",
"arc": "0000",
"cardEntryType": "ICC",
"type": "REFUND",
"transactionID": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9"
}
TSYSDummy rejects refunds synchronously with HTTP 400 when refundAllowed=false. ViscusDummy forwards to the terminal (202) and declines after card read. Handle both code paths.
Error — wrong card on linked refund
When refundOriginalCardOnly = true, the refund must be performed on the original card.
// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "ORIGINAL_CARD_REQUIRED_FOR_REFUND",
"cardEntryType": "ICC",
"type": "REFUND"
}
statusMessage: "ORIGINAL_CARD_REQUIRED_FOR_REFUND" is an English constant on this error path — safe to match programmatically.
Error — transaction not refundable
When a sale was processed while refundAllowed = false, the transaction is permanently flagged as non-refundable. This flag persists even after re-enabling the capability.
Escalate to support@handpoint.com — the Handpoint operations team can manually clear the non-refundable flag on specific transactions.
Refund Reversal (on terminal)
Reverses a previously issued refund, restoring the original refunded amount to the merchant. The terminal must be connected.
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refundReversal",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700668000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "6c22a900-a9f5-11f1-a943-f9c9f04151d9",
"requestedAmount": 5000,
"totalAmount": 5000,
"type": "REFUND_REVERSAL"
}
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | "refundReversal" |
serial_number | string | Yes | Terminal serial number |
terminal_type | string | Yes | PAX model — valid values |
amount | string | Yes | Amount in minor units. For acquirers that support partial reversal, the specified amount is reversed. For others, the full original refund amount is reversed regardless of this value. |
currency | string | Yes | ISO 4217 currency code |
originalTransactionId | string | Yes | transactionID from the refund to reverse |
customerReference | string | No | Free-text reference |
Reversal
Reverses all or part of an authorized transaction. Full reversals are always available. Partial reversals require partialReversalAllowed = true. The response is synchronous — no polling needed.
# Full reversal
curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"amount": "150.12",
"currency": "USD"
}'
// HTTP 200 — AUTHORISED (synchronous)
{
"finStatus": "AUTHORISED",
"transactionID": "6a1d41e0-a9f5-11f1-a943-f9c9f04151d9",
"type": "REVERSAL"
}
Omit amount (or pass the full original amount) for a full reversal. For a partial reversal, pass the specific amount to release.
POST /reversal — request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
originalGuid | string | Yes | — | transactionID from the original AUTHORISED sale result |
amount | string | No | Full original amount | Major-unit decimal — "50.04" = $50.04. Omit for full reversal. Required for partial reversal. |
currency | string | No | — | ISO 4217. Required when amount is provided. |
messageReasonCode | string | No | "CUSTOMER_CANCELLATION" | Reason for the reversal. "CUSTOMER_CANCELLATION" (default) — cardholder or merchant initiated. "TIMEOUT_WAITING_FOR_RESPONSE" — use when reversing because you did not receive an authorization response in time. |
Error — partial reversal not enabled
The only capability enforced at the gateway HTTP layer — the terminal is never involved.
curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"amount": "50.04",
"currency": "USD"
}'
// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Partial reversals are not supported",
"code": "3109",
"details": { "errorCode": "3109", "reason": "Partial reversals are not supported" }
}
}
Error — transaction not found
curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "00000000-0000-0000-0000-000000000000",
"currency": "USD"
}'
// HTTP 400
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Unable to find message to reverse.",
"code": "3153",
"details": { "errorCode": "3153", "httpStatus": 404 }
}
}
Error — already reversed
// HTTP 400
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Authorization has already been completed",
"code": "3052",
"details": { "errorCode": "3052", "httpStatus": 409 }
}
}
MOTO / Card-Not-Present
MOTO uses a stored card token. Requires supportsMoto = true on the merchant.
| Operation | Endpoint | Response model |
|---|---|---|
| Keyed entry sale / pre-auth / refund on terminal | POST /transactions | HTTP 202 → poll |
| Remote sale (back-office, no terminal) | POST /moto/sale | Synchronous |
| Remote refund (back-office, no terminal) | POST /moto/refund | Synchronous |
MOTO sale — keyed entry on terminal
# Step 1 — initiate (cardToken from a previous saleAndTokenizeCard)
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToSale",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"cardToken": "K33f40000000000093",
"transactionReference": "a1b2c3d4-0000-4000-8000-000000000001"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700645247 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"requestedAmount": 2000,
"totalAmount": 2000,
"type": "MOTO_SALE"
}
POST /transactions — moToSale — request parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
operation | string | Yes | — | "moToSale" |
serial_number | string | Yes | — | Terminal serial number |
terminal_type | string | Yes | — | PAX model — valid values |
amount | string | Yes | — | Minor-unit string — "2000" = $20.00. Digits only. |
currency | string | Yes | — | ISO 4217 |
cardToken | string | No | — | Stored card token. If omitted, terminal shows manual card-entry screen. |
channel | string | No | — | "MO" (mail order) or "TO" (telephone order) |
callbackUrl | string | No | — | HTTPS webhook endpoint |
token | string | No | — | Callback auth token |
customerReference | string | No | — | Free-text reference |
merchantAuth | array | No | — | Multi-MID override |
metadata | object | No | — | Up to 5 string fields, max 250 chars each |
billing | object | No | — | AVS — { "zipCode": string (required), "address": string (optional) } |
taxInformation | object | No | — | Level 2 — { "taxAmount": string, "taxExempt": boolean }. taxAmount in minor units, like amount. Must be sent with purchaseOrderNumber. taxAmount is required: "0" when taxExempt is true, greater than "0" otherwise, and never above amount. See Level 2 Purchasing Data. |
purchaseOrderNumber | string | No | — | Level 2 purchase order number. Must be sent with taxInformation. Alphanumeric, max 25 chars. |
transactionReference bug on moToSale (CUS-837)The transactionReference you send is ignored by the Cloud API for moToSale — the returned result contains a system-generated reference that does not match your value. Recovery via GET /transactions/{ref}/status will not work for keyed-entry MOTO. Use transactionResultId to poll instead, and store transactionID from the result for recovery. Status: open as of 2026-09-06.
MOTO refund — keyed entry on terminal
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToRefund",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700646000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "5f3a1100-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"requestedAmount": 2000,
"totalAmount": 2000,
"type": "MOTO_REFUND"
}
POST /transactions — moToRefund — request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | "moToRefund" |
serial_number | string | Yes | Terminal serial number |
terminal_type | string | Yes | PAX model — valid values |
amount | string | Yes | Minor-unit string — "2000" = $20.00. Digits only. |
currency | string | Yes | ISO 4217 |
originalTransactionId | string | No | transactionID from the original MOTO sale for a linked refund. Omit for unlinked. |
cardToken | string | No | Stored card token. If omitted, terminal shows manual card-entry screen. |
customerReference | string | No | Free-text reference |
MOTO reversal — keyed entry on terminal
# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToReversal",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700647000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "6031c200-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"type": "MOTO_REVERSAL"
}
POST /transactions — moToReversal — request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | "moToReversal" |
serial_number | string | Yes | Terminal serial number |
terminal_type | string | Yes | PAX model — valid values |
amount | string | Yes | Minor-unit string — "2000" = $20.00. Digits only. |
currency | string | Yes | ISO 4217 |
originalTransactionId | string | Yes | transactionID from the original MOTO sale to reverse |
customerReference | string | No | Free-text reference |