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.
| Method | Endpoint | Purpose |
|---|---|---|
POST | /v1/checkout | Create a checkout of any type |
GET | /v1/checkout/banks | List FPX banks |
GET | /v1/checkout/{id} | Read one checkout for reconciliation |
GET | /v1/checkout | List and filter checkouts |
Create checkout
/v1/checkoutAPI key + signatureCreates 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
| Header | Required | Description |
|---|---|---|
Idempotency-Key | Yes | UUID v4 persisted for this checkout before the first request |
Body — every type
| Field | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | BLOX_ACCOUNT, FPX_HOSTED, or FPX_DIRECT. No default — omitting it is a 400 |
addressTo | string | Yes | EVM or Solana address |
amount | integer | Yes | 1000–100000000 sen (RM10–RM1,000,000) |
tokenId | string (UUID) | Yes | Active token from GET /v1/wallet/networks |
redirectUrl | string (URL) | Yes | Valid redirect after payment |
title | string | Yes | 1–100 characters |
description | string | No | Maximum 500 characters |
Additional fields by type
| Field | BLOX_ACCOUNT | FPX_HOSTED | FPX_DIRECT |
|---|---|---|---|
feeMode | Rejected | Required | Required |
buyerName | — | — | Required, 1–100 characters |
bank | — | — | Required, a code from GET /v1/checkout/banks |
bankType | — | — | Required: 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
202or429with the same key. - Retry a rejected
400or422with a new key after correcting the request. - After a timeout or
5xx, reconcile by your uniquetitlebefore 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
| Status | code | When |
|---|---|---|
400 | INVALID_REQUEST | Idempotency-Key is missing or malformed, or tokenId is unavailable |
400 | VALIDATION_FAILED | A request field is invalid |
401 | UNAUTHORIZED | API key or request signature is missing or invalid |
403 | FEATURE_DISABLED | Checkout is not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
403 | ACCOUNT_FROZEN | The account is frozen |
403 | ACCOUNT_PENDING_VERIFICATION | The account is pending verification |
403 | ACCOUNT_SUSPENDED | Deposits are suspended for the account |
422 | VALIDATION_FAILED | The idempotency key was already used with a different body |
429 | RATE_LIMITED | The route or account rate limit was exceeded |
5xx | INTERNAL_ERROR | Unexpected 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
/v1/checkout/banksAPI keyResponse — 200
{
"banks": [
{
"code": "MBBEMYKL",
"name": "Maybank",
"displayName": "Maybank",
"logoUrl": "https://…",
"type": "01",
"active": true
}
]
}| Field | Type | Description |
|---|---|---|
code | string | Bank code; pass as bank on create |
name | string | Bank name |
displayName | string | Customer-facing bank name |
logoUrl | string (URL) | Bank logo |
type | string | "01" for retail (B2C), or "02" for corporate (B2B); pass it as bankType |
active | boolean | Only use banks where this is true |
Response is cached (~1 hour).
Error responses
| Status | code | When |
|---|---|---|
400 | INVALID_REQUEST | The bank provider could not return its bank list |
401 | UNAUTHORIZED | API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Direct Checkout is not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
5xx | INTERNAL_ERROR | Unexpected 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
| Field | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | FPX_DIRECT |
addressTo | string | Yes | EVM or Solana address |
amount | integer | Yes | 1000–100000000 sen (RM10–RM1,000,000) |
tokenId | string (UUID) | Yes | Active token from GET /v1/wallet/networks |
redirectUrl | string (URL) | Yes | Valid redirect after payment |
title | string | Yes | 1–100 characters; use your reconciliation handle |
description | string | No | Maximum 500 characters |
buyerName | string | Yes | 1–100 trimmed characters |
bank | string | Yes | Active code from the banks list |
bankType | string | Yes | RETAIL, CORPORATE, "01", or "02" |
feeMode | string | Yes | DEDUCT_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 returns400 INSUFFICIENT_BALANCEand 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
202or429with the same key. - Retry a rejected
400or422with a new key after correcting the request. - After a timeout or
5xx, reconcile by your uniquetitlebefore 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
| Status | code | When |
|---|---|---|
400 | INVALID_REQUEST | Idempotency-Key is missing or malformed, tokenId is unavailable, or the amount cannot cover the fee |
400 | VALIDATION_FAILED | A request field is invalid |
401 | UNAUTHORIZED | API key or request signature is missing or invalid |
403 | FEATURE_DISABLED | Direct Checkout is not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
403 | ACCOUNT_FROZEN | The account is frozen |
403 | ACCOUNT_PENDING_VERIFICATION | The account is pending verification |
403 | ACCOUNT_SUSPENDED | Deposits are suspended for the account |
422 | VALIDATION_FAILED | The idempotency key was already used with a different body |
429 | RATE_LIMITED | The route or account rate limit was exceeded |
5xx | INTERNAL_ERROR | Unexpected 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
/v1/checkout/{id}API keyReturns a checkout by checkoutId. Use webhooks for status changes; use this endpoint for reconciliation or missed webhook recovery.
| Path parameter | Type | Description |
|---|---|---|
id | string (UUID) | Checkout owned by your account |
Response — 200: Checkout object. Unknown and cross-account IDs return 404 NOT_FOUND.
Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | API key is missing, unknown, or inactive |
403 | ACCOUNT_TERMINATED | The account is terminated |
404 | NOT_FOUND | The checkout does not exist or belongs to another account |
5xx | INTERNAL_ERROR | Unexpected 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.
/v1/checkoutAPI keyQuery parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
limit | integer | No | 1–100, default 25 | Records per page |
cursor | string | No | checkoutId of the last item from the previous page | Omit for the first page |
status | string | No | Checkout status | Filter by status |
startDt | string | No | ISO 8601 datetime | Created on or after |
endDt | string | No | ISO 8601 datetime | Created on or before |
Status filter values
| Status | Description |
|---|---|
CREATED | Checkout created, awaiting customer |
PENDING | Customer selected wallet, ready to pay |
PROCESSING | Payment initiated, waiting for confirmation |
COMPLETED | Payment successful |
FAILED | Payment failed |
CANCELLED | Checkout cancelled |
REJECTED | Payment 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
| Field | Type | Description |
|---|---|---|
data | array | Checkouts, newest first |
hasMore | boolean | Another page exists |
nextCursor | string | null | Pass 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
| Status | code | When |
|---|---|---|
400 | VALIDATION_FAILED | A query parameter is malformed or outside its constraint |
401 | UNAUTHORIZED | API key is missing, unknown, or inactive |
403 | ACCOUNT_TERMINATED | The account is terminated |
5xx | INTERNAL_ERROR | Unexpected 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
| Field | Type | Description |
|---|---|---|
checkoutId | string (UUID) | Unique checkout identifier; matches checkoutId on webhook events |
type | string | BLOX_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 |
status | string | Current checkout status |
amount | string | Payment amount in sen |
fee | string | Fee in sen; always "0" for BLOX_ACCOUNT and when no checkout fee is configured |
netAmount | string | Amount settled on-chain. amount - fee under DEDUCT_FROM_AMOUNT, and equal to amount under CHARGE_TO_PREFUND |
addressTo | string | Address receiving MYRC |
tokenId | string (UUID) | Token delivered; uses IDs from GET /v1/wallet/networks |
token | object | { id, name, symbol } |
title | string | Title supplied on create |
description | string | null | Description supplied on create |
redirectUrl | string | Redirect supplied on create |
txHash | string | null | On-chain transaction hash when available |
paidAt | string (ISO 8601) | null | Time the transfer confirmed |
expiresAt | string (ISO 8601) | Checkout expiration time |
createdAt / updatedAt | string (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, orREJECTED.
The canonical status transitions and expiration rules are in Onramp lifecycle.