Skip to content
LogoLogo

Onramp API Reference

All Onramp endpoints in integration order. Use https://api.sandbox.blox.my while testing and https://api.blox.my in production.

Read requests require blox-api-key. Create requests require the API key, an HTTP message signature, and an Idempotency-Key UUID v4 persisted before the first request.

Every error uses { "code": "…", "message": "…", "requestId": "…" }. Branch on code, never message; see Onramp Errors for retry guidance.

MethodEndpointPurpose
POST/v1/checkoutCreate a checkout of any type
GET/v1/checkout/banksList FPX banks
GET/v1/checkout/{id}Read one checkout for reconciliation
GET/v1/checkoutList and filter checkouts

Create checkout

POST/v1/checkoutAPI key + signature

Creates a checkout link of the type you name. The link expires after 20 minutes, and the endpoint is limited to 120 requests per minute per account.

Header

HeaderRequiredDescription
Idempotency-KeyYesUUID v4 persisted for this checkout before the first request

Body — every type

FieldTypeRequiredConstraints
typestringYesBLOX_ACCOUNT, FPX_HOSTED, or FPX_DIRECT. No default — omitting it is a 400
addressTostringYesEVM or Solana address
amountintegerYes1000100000000 sen (RM10–RM1,000,000)
tokenIdstring (UUID)YesActive token from GET /v1/wallet/networks
redirectUrlstring (URL)YesValid redirect after payment
titlestringYes1–100 characters
descriptionstringNoMaximum 500 characters

Additional fields by type

FieldBLOX_ACCOUNTFPX_HOSTEDFPX_DIRECT
feeModeRejectedRequiredRequired
buyerNameRequired, 1–100 characters
bankRequired, a code from GET /v1/checkout/banks
bankTypeRequired: RETAIL/CORPORATE, or the wire codes 01/02

feeMode is DEDUCT_FROM_AMOUNT or CHARGE_TO_PREFUND. Sending it on a BLOX_ACCOUNT checkout is a 400, not an ignored field — that product has no MYR leg to charge against, and dropping it silently is how you end up believing a fee applies.

FPX_HOSTED does not take buyerName, bank, or bankType: the customer supplies them on the BLOX page, which is also when the FPX bill is created.

EVM addresses use 0x plus 40 hexadecimal characters; Solana addresses use Base58.

Response — 200

{
  "checkoutId": "8f14e45f-ceea-467f-a830-5e3e3c7e2b8a",
  "checkoutUrl": "https://checkout.blox.my/8f14e45f-ceea-467f-a830-5e3e3c7e2b8a?s=checkout_signature",
  "expiresAt": "2026-02-03T15:20:00.000Z"
}

Use the complete checkoutUrl, including its query string. Unpaid checkouts become FAILED after expiry. A 202 response means the original request for that key is still processing; sign a fresh request and retry with the same key.

Idempotency rules

  • Persist one UUID v4 before sending a logical checkout.
  • Retry 202 or 429 with the same key.
  • Retry a rejected 400 or 422 with a new key after correcting the request.
  • After a timeout or 5xx, reconcile by your unique title before retrying with the same key.
  • Reusing a key with a different body returns 422 VALIDATION_FAILED.

See Onramp Errors for the complete code and retry table.

Error responses
StatuscodeWhen
400INVALID_REQUESTIdempotency-Key is missing or malformed, or tokenId is unavailable
400VALIDATION_FAILEDA request field is invalid
401UNAUTHORIZEDAPI key or request signature is missing or invalid
403FEATURE_DISABLEDCheckout is not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
403ACCOUNT_FROZENThe account is frozen
403ACCOUNT_PENDING_VERIFICATIONThe account is pending verification
403ACCOUNT_SUSPENDEDDeposits are suspended for the account
422VALIDATION_FAILEDThe idempotency key was already used with a different body
429RATE_LIMITEDThe route or account rate limit was exceeded
5xxINTERNAL_ERRORUnexpected BLOX failure; report requestId

Error examples

400 INVALID_REQUEST
{
  "code": "INVALID_REQUEST",
  "message": "Missing idempotency key",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
400 VALIDATION_FAILED

For a request with an empty title:

{
  "code": "VALIDATION_FAILED",
  "message": "title: Title is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
401 UNAUTHORIZED
{
  "code": "UNAUTHORIZED",
  "message": "API key is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 FEATURE_DISABLED
{
  "code": "FEATURE_DISABLED",
  "message": "Merchant checkout feature not enabled: checkout_with_blox_account",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_TERMINATED
{
  "code": "ACCOUNT_TERMINATED",
  "message": "Your account has been terminated. Please contact support.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_FROZEN
{
  "code": "ACCOUNT_FROZEN",
  "message": "Your account has been frozen. Please contact support.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_PENDING_VERIFICATION
{
  "code": "ACCOUNT_PENDING_VERIFICATION",
  "message": "Your account is pending verification. This action is not allowed for this account at this time.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_SUSPENDED
{
  "code": "ACCOUNT_SUSPENDED",
  "message": "Your account is suspended. Deposits are not allowed for this account.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
422 VALIDATION_FAILED
{
  "code": "VALIDATION_FAILED",
  "message": "Idempotency key used with different request payload",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
429 RATE_LIMITED
{
  "code": "RATE_LIMITED",
  "message": "Too many checkout creations. Please try again later.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}

List checkout banks

GET/v1/checkout/banksAPI key

Response — 200

{
  "banks": [
    {
      "code": "MBBEMYKL",
      "name": "Maybank",
      "displayName": "Maybank",
      "logoUrl": "https://…",
      "type": "01",
      "active": true
    }
  ]
}
FieldTypeDescription
codestringBank code; pass as bank on create
namestringBank name
displayNamestringCustomer-facing bank name
logoUrlstring (URL)Bank logo
typestring"01" for retail (B2C), or "02" for corporate (B2B); pass it as bankType
activebooleanOnly use banks where this is true

Response is cached (~1 hour).

Error responses
StatuscodeWhen
400INVALID_REQUESTThe bank provider could not return its bank list
401UNAUTHORIZEDAPI key is missing, unknown, or inactive
403FEATURE_DISABLEDDirect Checkout is not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
5xxINTERNAL_ERRORUnexpected BLOX failure; report requestId

Error examples

400 INVALID_REQUEST
{
  "code": "INVALID_REQUEST",
  "message": "Failed to get banks",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}

Create checkout — direct FPX

The same POST /v1/checkout endpoint, with "type": "FPX_DIRECT" and a bank selected from GET /v1/checkout/banks. Shown separately because it is the only type that carries bank fields.

For "type": "FPX_HOSTED", send the body from Create checkout plus feeMode and omit buyerName, bank, and bankType — the customer supplies those on the BLOX page, which is also when the FPX bill is created.

Body

FieldTypeRequiredConstraints
typestringYesFPX_DIRECT
addressTostringYesEVM or Solana address
amountintegerYes1000100000000 sen (RM10–RM1,000,000)
tokenIdstring (UUID)YesActive token from GET /v1/wallet/networks
redirectUrlstring (URL)YesValid redirect after payment
titlestringYes1–100 characters; use your reconciliation handle
descriptionstringNoMaximum 500 characters
buyerNamestringYes1–100 trimmed characters
bankstringYesActive code from the banks list
bankTypestringYesRETAIL, CORPORATE, "01", or "02"
feeModestringYesDEDUCT_FROM_AMOUNT or CHARGE_TO_PREFUND

feeMode is required on both FPX types, and is decided per checkout rather than per account:

  • DEDUCT_FROM_AMOUNT — your on-chain settlement is net of the fee.
  • CHARGE_TO_PREFUND — settlement is gross; the fee comes out of your checkout prefund balance instead. It is reserved the moment the link is created, not when the buyer pays, and if the balance cannot cover it the create call returns 400 INSUFFICIENT_BALANCE and no link exists.

It is not defaulted because it decides what reaches your address. If your account has no fee configured, the mode has no effect but is still required.

Response — 200

{
  "checkoutId": "8f14e45f-ceea-467f-a830-5e3e3c7e2b8a",
  "checkoutUrl": "https://checkout.blox.my/fpx/8f14e45f-ceea-467f-a830-5e3e3c7e2b8a?s=checkout_signature",
  "expiresAt": "2026-02-03T15:20:00.000Z"
}

Use the complete checkoutUrl, including its query string. The link expires after 20 minutes. A 202 response means the original request for that key is still processing; sign a fresh request and retry with the same key.

The buyer details supplied on create (buyerName, bank, bankType) are not echoed by reads; store them against checkoutId. Handle Checkout webhook events and use GET /v1/checkout/{id} for reconciliation.

Idempotency rules

  • Persist one UUID v4 before sending a logical checkout.
  • Retry 202 or 429 with the same key.
  • Retry a rejected 400 or 422 with a new key after correcting the request.
  • After a timeout or 5xx, reconcile by your unique title before retrying with the same key.
  • Reusing a key with a different body returns 422 VALIDATION_FAILED.

See Onramp Errors for the complete code and retry table.

Error responses
StatuscodeWhen
400INVALID_REQUESTIdempotency-Key is missing or malformed, tokenId is unavailable, or the amount cannot cover the fee
400VALIDATION_FAILEDA request field is invalid
401UNAUTHORIZEDAPI key or request signature is missing or invalid
403FEATURE_DISABLEDDirect Checkout is not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
403ACCOUNT_FROZENThe account is frozen
403ACCOUNT_PENDING_VERIFICATIONThe account is pending verification
403ACCOUNT_SUSPENDEDDeposits are suspended for the account
422VALIDATION_FAILEDThe idempotency key was already used with a different body
429RATE_LIMITEDThe route or account rate limit was exceeded
5xxINTERNAL_ERRORUnexpected BLOX failure; report requestId

Error examples

400 INVALID_REQUEST
{
  "code": "INVALID_REQUEST",
  "message": "Missing idempotency key",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
400 VALIDATION_FAILED

For a request with an empty buyerName:

{
  "code": "VALIDATION_FAILED",
  "message": "buyerName: Buyer name is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
401 UNAUTHORIZED
{
  "code": "UNAUTHORIZED",
  "message": "API key is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 FEATURE_DISABLED
{
  "code": "FEATURE_DISABLED",
  "message": "Merchant checkout feature not enabled: checkout_direct",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_TERMINATED
{
  "code": "ACCOUNT_TERMINATED",
  "message": "Your account has been terminated. Please contact support.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_FROZEN
{
  "code": "ACCOUNT_FROZEN",
  "message": "Your account has been frozen. Please contact support.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_PENDING_VERIFICATION
{
  "code": "ACCOUNT_PENDING_VERIFICATION",
  "message": "Your account is pending verification. This action is not allowed for this account at this time.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
403 ACCOUNT_SUSPENDED
{
  "code": "ACCOUNT_SUSPENDED",
  "message": "Your account is suspended. Deposits are not allowed for this account.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
422 VALIDATION_FAILED
{
  "code": "VALIDATION_FAILED",
  "message": "Idempotency key used with different request payload",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
429 RATE_LIMITED
{
  "code": "RATE_LIMITED",
  "message": "Too many checkout creations. Please try again later.",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}

Get checkout

GET/v1/checkout/{id}API key

Returns a checkout by checkoutId. Use webhooks for status changes; use this endpoint for reconciliation or missed webhook recovery.

Path parameterTypeDescription
idstring (UUID)Checkout owned by your account

Response — 200: Checkout object. Unknown and cross-account IDs return 404 NOT_FOUND.

Error responses
StatuscodeWhen
401UNAUTHORIZEDAPI key is missing, unknown, or inactive
403ACCOUNT_TERMINATEDThe account is terminated
404NOT_FOUNDThe checkout does not exist or belongs to another account
5xxINTERNAL_ERRORUnexpected BLOX failure; report requestId

Error examples

401 UNAUTHORIZED
{
  "code": "UNAUTHORIZED",
  "message": "API key is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}

List checkouts

Query all checkouts for your account with pagination and filtering.

GET/v1/checkoutAPI key

Query parameters

FieldTypeRequiredConstraintsDescription
limitintegerNo1–100, default 25Records per page
cursorstringNocheckoutId of the last item from the previous pageOmit for the first page
statusstringNoCheckout statusFilter by status
startDtstringNoISO 8601 datetimeCreated on or after
endDtstringNoISO 8601 datetimeCreated on or before

Status filter values

StatusDescription
CREATEDCheckout created, awaiting customer
PENDINGCustomer selected wallet, ready to pay
PROCESSINGPayment initiated, waiting for confirmation
COMPLETEDPayment successful
FAILEDPayment failed
CANCELLEDCheckout cancelled
REJECTEDPayment provider refused the payment

Request example

Response — 200

Each row has the checkout object shape.

{
  "data": [
    {
      "checkoutId": "8f14e45f-ceea-467f-a830-5e3e3c7e2b8a",
      "status": "COMPLETED",
      "amount": "15000",
      "fee": "0",
      "netAmount": "15000",
      "addressTo": "0x742d35Cc...",
      "tokenId": "550e8400-...",
      "token": { "id": "550e8400-...", "name": "MYRC", "symbol": "MYRC" },
      "title": "Order #123",
      "description": null,
      "redirectUrl": "https://yoursite.com/success",
      "txHash": "0xabc123def456...",
      "paidAt": "2026-02-03T15:05:30.000Z",
      "expiresAt": "2026-02-03T15:20:00.000Z",
      "createdAt": "2026-02-03T15:00:00.000Z",
      "updatedAt": "2026-02-03T15:05:30.000Z"
    }
  ],
  "hasMore": true,
  "nextCursor": "8f14e45f-ceea-467f-a830-5e3e3c7e2b8a"
}

Response fields

FieldTypeDescription
dataarrayCheckouts, newest first
hasMorebooleanAnother page exists
nextCursorstring | nullPass as cursor to fetch the next page; null on the last one

The list is cursor paginated — iterate until hasMore is false. There is no total.

Error responses
StatuscodeWhen
400VALIDATION_FAILEDA query parameter is malformed or outside its constraint
401UNAUTHORIZEDAPI key is missing, unknown, or inactive
403ACCOUNT_TERMINATEDThe account is terminated
5xxINTERNAL_ERRORUnexpected BLOX failure; report requestId

Error examples

400 VALIDATION_FAILED

For ?limit=101:

{
  "code": "VALIDATION_FAILED",
  "message": "limit: Number must be less than or equal to 100",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}

Shared response schema

Checkout object

FieldTypeDescription
checkoutIdstring (UUID)Unique checkout identifier; matches checkoutId on webhook events
typestringBLOX_ACCOUNT, FPX_HOSTED, or FPX_DIRECT — the same value you created with, so a mixed list can be told apart. Checkouts created before type existed read BLOX_ACCOUNT, which is what they are
statusstringCurrent checkout status
amountstringPayment amount in sen
feestringFee in sen; always "0" for BLOX_ACCOUNT and when no checkout fee is configured
netAmountstringAmount settled on-chain. amount - fee under DEDUCT_FROM_AMOUNT, and equal to amount under CHARGE_TO_PREFUND
addressTostringAddress receiving MYRC
tokenIdstring (UUID)Token delivered; uses IDs from GET /v1/wallet/networks
tokenobject{ id, name, symbol }
titlestringTitle supplied on create
descriptionstring | nullDescription supplied on create
redirectUrlstringRedirect supplied on create
txHashstring | nullOn-chain transaction hash when available
paidAtstring (ISO 8601) | nullTime the transfer confirmed
expiresAtstring (ISO 8601)Checkout expiration time
createdAt / updatedAtstring (ISO 8601)Creation and last update times

See Fees for how feeMode, fee, and netAmount interact.


Reconcile checkout status

Use checkout webhooks as the primary signal. Read a checkout to reconcile a missed event or confirm a CANCELLED or REJECTED outcome, because those statuses do not emit checkout.updated.

If polling is required:

  • Poll every 5 seconds while a checkout is PROCESSING.
  • Stop at COMPLETED, FAILED, CANCELLED, or REJECTED.

The canonical status transitions and expiration rules are in Onramp lifecycle.