#!/usr/bin/env bash
# Authorize only (reserve funds, capture later). Any 2xx is a response — branch
# on data.status. On a timeout/5xx/PENDING, retry with the SAME request_id;
# never mint a new one for the same order attempt.
set -euo pipefail
API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
: "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token}"
curl -sS -X POST "$API_BASE/v1/transactions/auth" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
-d @request.json#!/usr/bin/env python3
"""Authorize only (reserve funds, capture later). Any 2xx is a response — branch
on data.status. On a timeout/5xx/PENDING, retry with the SAME request_id;
never mint a new one for the same order attempt.
Shared result pattern: any 2xx is a response you branch on 'status'. On a
network timeout, a 5xx, or status 'PENDING', retry with the SAME
request_id -- never mint a new one for the same attempt.
"""
import json
import os
import random
import time
from pathlib import Path
import requests
API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")
def backoff_seconds(attempt: int) -> float:
"""Exponential backoff with jitter: attempt 1 waits ~0.25-0.5s, doubling
each attempt, capped at 4s -- avoids hammering the gateway in a loop."""
base = min(0.25 * 2 ** (attempt - 1), 4.0)
return base + random.random() * base
def create_authorization(max_attempts: int = 3) -> dict:
body = json.loads((Path(__file__).parent / "request.json").read_text())
headers = {"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"}
for attempt in range(1, max_attempts + 1):
try:
resp = requests.post(f"{API_BASE}/v1/transactions/auth", json=body, headers=headers, timeout=30)
except requests.exceptions.Timeout:
if attempt == max_attempts:
raise
time.sleep(backoff_seconds(attempt))
continue
if resp.status_code >= 500:
if attempt == max_attempts:
raise RuntimeError(f"server error {resp.status_code} after {attempt} attempts")
time.sleep(backoff_seconds(attempt))
continue
payload = resp.json()
if not resp.ok:
code = payload.get("type") or payload.get("code")
raise RuntimeError(f"request failed: {code} ({resp.status_code})")
if payload["data"]["status"] == "PENDING":
if attempt == max_attempts:
return payload
time.sleep(backoff_seconds(attempt))
continue
return payload # branch on data.status
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(json.dumps(create_authorization(), indent=2))
{
"status": "ok",
"request_id": "req_1b2c3d4e5f60",
"data": {
"id": "1b2c3d4e-5f60-4718-9a2b-3c4d5e6f7081",
"request_id": "ord-1002-auth-1",
"type": "AUTHORIZE",
"status": "AUTHORIZED",
"amount": 10000,
"currency": "SGD",
"payment_method_type": "card",
"order_reference": "ORD-1002",
"response_code": "00",
"parent_transaction_id": null,
"switch_request_id": "sw-1b2c3d4e",
"created_at": "2026-09-14T10:00:00.000Z",
"updated_at": "2026-09-14T10:00:01.000Z"
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"request_id": "<string>",
"code": "<string>",
"retry_allowed": true,
"errors": [
{
"pointer": "<string>",
"parameter": "<string>",
"code": "<string>",
"detail": "<string>"
}
]
}{
"detail": "Missing or invalid Bearer token.",
"status": 401,
"title": "Authentication Required",
"type": "urn:radiumone:gateway:authentication-required"
}{
"detail": "Insufficient permissions for this operation.",
"status": 403,
"title": "Permission Denied",
"type": "urn:radiumone:gateway:permission-denied"
}{
"detail": "The requested resource does not exist.",
"status": 404,
"title": "Not Found",
"type": "urn:radiumone:gateway:not-found"
}{
"detail": "A resource with that identifier already exists.",
"status": 409,
"title": "Conflict",
"type": "urn:radiumone:gateway:conflict"
}{
"detail": "The payment token has expired or its card data is no longer available.",
"status": 410,
"title": "Gone",
"type": "urn:radiumone:gateway:gone"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"request_id": "<string>",
"code": "<string>",
"retry_allowed": true,
"errors": [
{
"pointer": "<string>",
"parameter": "<string>",
"code": "<string>",
"detail": "<string>"
}
]
}{
"detail": "An unexpected error occurred.",
"status": 500,
"title": "Internal Server Error",
"type": "urn:radiumone:gateway:internal-server-error"
}{
"detail": "A downstream dependency is unavailable or did not respond in time.",
"status": 503,
"title": "Service Unavailable",
"type": "urn:radiumone:gateway:service-unavailable"
}Authorize - Payments API
Authorize a card payment to hold funds without capturing them, then capture or void it later. A duplicate request ID returns the original response.
#!/usr/bin/env bash
# Authorize only (reserve funds, capture later). Any 2xx is a response — branch
# on data.status. On a timeout/5xx/PENDING, retry with the SAME request_id;
# never mint a new one for the same order attempt.
set -euo pipefail
API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
: "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token}"
curl -sS -X POST "$API_BASE/v1/transactions/auth" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
-d @request.json#!/usr/bin/env python3
"""Authorize only (reserve funds, capture later). Any 2xx is a response — branch
on data.status. On a timeout/5xx/PENDING, retry with the SAME request_id;
never mint a new one for the same order attempt.
Shared result pattern: any 2xx is a response you branch on 'status'. On a
network timeout, a 5xx, or status 'PENDING', retry with the SAME
request_id -- never mint a new one for the same attempt.
"""
import json
import os
import random
import time
from pathlib import Path
import requests
API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")
def backoff_seconds(attempt: int) -> float:
"""Exponential backoff with jitter: attempt 1 waits ~0.25-0.5s, doubling
each attempt, capped at 4s -- avoids hammering the gateway in a loop."""
base = min(0.25 * 2 ** (attempt - 1), 4.0)
return base + random.random() * base
def create_authorization(max_attempts: int = 3) -> dict:
body = json.loads((Path(__file__).parent / "request.json").read_text())
headers = {"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"}
for attempt in range(1, max_attempts + 1):
try:
resp = requests.post(f"{API_BASE}/v1/transactions/auth", json=body, headers=headers, timeout=30)
except requests.exceptions.Timeout:
if attempt == max_attempts:
raise
time.sleep(backoff_seconds(attempt))
continue
if resp.status_code >= 500:
if attempt == max_attempts:
raise RuntimeError(f"server error {resp.status_code} after {attempt} attempts")
time.sleep(backoff_seconds(attempt))
continue
payload = resp.json()
if not resp.ok:
code = payload.get("type") or payload.get("code")
raise RuntimeError(f"request failed: {code} ({resp.status_code})")
if payload["data"]["status"] == "PENDING":
if attempt == max_attempts:
return payload
time.sleep(backoff_seconds(attempt))
continue
return payload # branch on data.status
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(json.dumps(create_authorization(), indent=2))
{
"status": "ok",
"request_id": "req_1b2c3d4e5f60",
"data": {
"id": "1b2c3d4e-5f60-4718-9a2b-3c4d5e6f7081",
"request_id": "ord-1002-auth-1",
"type": "AUTHORIZE",
"status": "AUTHORIZED",
"amount": 10000,
"currency": "SGD",
"payment_method_type": "card",
"order_reference": "ORD-1002",
"response_code": "00",
"parent_transaction_id": null,
"switch_request_id": "sw-1b2c3d4e",
"created_at": "2026-09-14T10:00:00.000Z",
"updated_at": "2026-09-14T10:00:01.000Z"
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"request_id": "<string>",
"code": "<string>",
"retry_allowed": true,
"errors": [
{
"pointer": "<string>",
"parameter": "<string>",
"code": "<string>",
"detail": "<string>"
}
]
}{
"detail": "Missing or invalid Bearer token.",
"status": 401,
"title": "Authentication Required",
"type": "urn:radiumone:gateway:authentication-required"
}{
"detail": "Insufficient permissions for this operation.",
"status": 403,
"title": "Permission Denied",
"type": "urn:radiumone:gateway:permission-denied"
}{
"detail": "The requested resource does not exist.",
"status": 404,
"title": "Not Found",
"type": "urn:radiumone:gateway:not-found"
}{
"detail": "A resource with that identifier already exists.",
"status": 409,
"title": "Conflict",
"type": "urn:radiumone:gateway:conflict"
}{
"detail": "The payment token has expired or its card data is no longer available.",
"status": 410,
"title": "Gone",
"type": "urn:radiumone:gateway:gone"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"request_id": "<string>",
"code": "<string>",
"retry_allowed": true,
"errors": [
{
"pointer": "<string>",
"parameter": "<string>",
"code": "<string>",
"detail": "<string>"
}
]
}{
"detail": "An unexpected error occurred.",
"status": 500,
"title": "Internal Server Error",
"type": "urn:radiumone:gateway:internal-server-error"
}{
"detail": "A downstream dependency is unavailable or did not respond in time.",
"status": 503,
"title": "Service Unavailable",
"type": "urn:radiumone:gateway:service-unavailable"
}Authorizations
Bearer access token from POST /v1/auth/token. Treat it as an opaque string — do not depend on its internal encoding, which has changed before and isn't part of the contract.
Body
Request body for POST /v1/transactions/auth.
Authorise-only: reserves funds without capturing. Use CAPTURE to complete. Idempotent via request_id scoped to the authenticated merchant.
Amount to authorize, as a {currency, value} money object.
Show child attributes
Show child attributes
Card group carrying the network token (no raw PAN).
Show child attributes
Show child attributes
Transaction channel -- the source/manner of the payment. Shapes routing candidate selection and the capability/operation constraints applied downstream (acquirer_channel + acquirer_channel_operation gating).
CARD_PRESENT, ECOMMERCE, MOTO, PAYMENT_LINK, IN_APP, RECURRING Merchant-supplied idempotency key.
8 - 64EMV chip data read from the card, for card-present authorizations. Send it either as the hex string your terminal produced or as a map of EMV tag to hex value. Omit it for online payments made with a token.
Optional merchant-supplied metadata.
Merchant's order/cart reference for this authorization (common in ecommerce). Stored, searchable via the transaction list filter, and forwarded to the acquirer for reconciliation. Capture/void/refund inherit it from this transaction, so one acquirer-side lookup returns the whole order. Acquirers impose their own limits and character rules (commonly 20 characters, alphanumeric) and will shorten the value to fit, so prefer short references using letters, digits, '-', '.' and '_', and put the varying part LAST -- values are shortened from the front.
1283DS result (one of {ref} | {mode:non_payer_auth} | {cavv,...}); absent == non-payer-auth.
Show child attributes
Show child attributes
Response
Successful Response
Standard success envelope. Every successful response has this shape, with the operation's own payload under data.
The operation's result. Its shape is documented per operation; omitted on responses that carry no payload.
Show child attributes
Show child attributes
Optional human-readable note. Omitted from the response when not set, which is the case for every payment operation today. Never parse it.
Correlation ID for this HTTP request, for logs and support. Send your own in the X-Request-Id header (letters, digits and hyphens, up to 36 characters -- other characters are stripped) or the gateway generates one. This is NOT the request_id idempotency key you send in a transaction body; the two are unrelated.
Always ok on a successful (2xx) response. Errors use a different body shape entirely (RFC 9457 problem details), so branch on the HTTP status code, not on this field.