> For the complete documentation index, see [llms.txt](https://docs.idrx.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.idrx.co/api/transaction-api/post-api-transaction-mint-request.md).

# POST /api/transaction/mint-request

{% hint style="info" %}
Check out [this page](/introduction/supported-chain-and-contract-address.md) to see the list of chains where IDRX is available and [this page](/integration/processing-mint-idrx-requests/getting-other-stablecoins.md) to see the list of supported other stablecoins.
{% endhint %}

{% hint style="info" %}

* The balance will be processed and credited to \`**`` destinationWalletAddress` ``** max 24 hours after your request is submitted.
* Minimum transaction:
  * Minting IDRX is Rp20,000 IDR
  * Minting other stablecoins are $2 USD
* Maximum transaction:
  * Minting IDRX is 1,000,000,000 IDR.
  * Minting other stablecoins are 5,555 USD
* For transactions more than the maximum limit, please send your request to <support@idrx.co> to be processed.
* The transaction will be automatically canceled if you have not made a payment within 24 hours.
* IDRX is not responsible for deposit errors from incorrect Virtual Account numbers. Refunds for payments made to Virtual Account numbers under a different name will be processed within 14 business days.
  {% endhint %}

```
POST https://idrx.co/api/transaction/mint-request
```

### Quick start

```bash
curl -X POST 'https://idrx.co/api/transaction/mint-request' \
  -H 'idrx-api-key: <API_KEY>' \
  -H 'idrx-api-sig: <SIGNATURE>' \
  -H 'idrx-api-ts: <TIMESTAMP>' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: my-app/1.0' \
  -d '{
    "toBeMinted": "20000",
    "destinationWalletAddress": "0x7D1398C397C64368B7079bD5b3EE904aaaf8A495",
    "networkChainId": "8453",
    "returnUrl": "https://your-app.example.com/callback",
    "requestType": "idrx",
    "paymentProvider": "snap"
  }'
```

Redirect the user to `data.paymentUrl` from the response. After payment, IDRX is delivered to the wallet — usually within minutes.

> For end-to-end integration including status tracking and reconciliation, see [Processing Mint IDRX Requests](/integration/processing-mint-idrx-requests.md#overview).

***

### Authentication

Every request needs four headers:

| Header         | Value                                     |
| -------------- | ----------------------------------------- |
| `idrx-api-key` | Your API key                              |
| `idrx-api-sig` | HMAC-SHA256 signature (see formula below) |
| `idrx-api-ts`  | Unix timestamp in milliseconds            |
| `User-Agent`   | Custom string like `my-app/1.0` (why)     |

#### Signature formula

```
HMAC-SHA256(secret, METHOD + ":" + PATH + ":" + SHA256(body) + ":" + timestamp)
```

For GET requests with no body, use `SHA256("")`.

Full guide: [Generating a Signature](/api/generating-a-signature.md)

API keys are issued via:

```http
POST /api/auth/generate-api-key
```

{% hint style="warning" %}
**Custom `User-Agent` required.** Defaults from `curl`, `Python-urllib`, or generic SDK strings are blocked at the edge with HTTP `403` (Cloudflare error 1010).
{% endhint %}

***

### Parameters

| Parameter                  | Type   | Required | Description                                                                                                                                                                                              |
| -------------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `toBeMinted`               | string | ✓        | Amount to mint. Min 20,000 IDR or 2 USD. Max 1,000,000,000 IDR or 5,555 USD. Must be a whole number on Polygon and BNB Chain, and may carry at most 2 decimals on the other chains — see the note below. |
| `destinationWalletAddress` | string | ✓        | Recipient wallet on the target chain.                                                                                                                                                                    |
| `networkChainId`           | string | ✓        | Target chain ID. Supported chains →                                                                                                                                                                      |
| `returnUrl`                | string | Flow A   | HTTPS URL the customer returns to after checkout. Required for Flow A. Not used in Flow B — there is no hosted page to return from, so you may omit it.                                                  |
| `requestType`              | string | —        | `"idrx"` (default) or `"usdt"`. Other tokens →                                                                                                                                                           |
| `usdtRequested`            | string | —        | Required when `requestType` is `"usdt"`. Amount of the target stablecoin the customer expects to receive.                                                                                                |
| `expiryPeriod`             | number | —        | Payment window in minutes. Default `120`.                                                                                                                                                                |
| `productDetails`           | string | —        | Custom note on payment page. Max 255 chars.                                                                                                                                                              |
| `paymentProvider`          | string | ✓        | Set to `"snap"`.                                                                                                                                                                                         |
| `paymentMethod`            | string | —        | Flow B only. `"va"`. When set, the response carries the Virtual Account number directly and no hosted checkout page is created. Requires `channelId`.                                                    |
| `channelId`                | string | —        | Required whenever `paymentMethod` is set. The bank channel — `"MANDIRI"` or `"BRI"`.                                                                                                                     |

{% hint style="warning" %}
**`toBeMinted` cannot be finer than the token itself.** IDRX has **0 decimals on Polygon and BNB Chain** — one IDRX is the smallest unit there, so amounts must be whole numbers. On every other chain IDRX has 2 decimals. Amounts are never silently rounded for you: send `"556931.42"` to Polygon and the request is rejected with

```
400 invalid toBeMinted: IDRX on chainId 137 supports 0 decimal place(s), got 556931.42
```

Round the amount in your own system before calling this endpoint, so the figure you show the customer is the figure that gets minted. Trailing zeros are fine — `"20000.00"` is accepted anywhere, since it carries no real precision. Decimals per chain: [Supported Chain and Contract Address](/introduction/supported-chain-and-contract-address.md).
{% endhint %}

***

### Choosing a flow

Every mint order goes through the same endpoint, the same authentication, and the same status tracking. What differs is who renders the payment screen.

|                              | Send                                                      | You get back                             | Customer experience                                              |
| ---------------------------- | --------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------- |
| **Flow A — Hosted checkout** | `paymentProvider: "snap"`                                 | `checkoutUrl` (mirrored as `paymentUrl`) | Redirected to a payment page where they pick a method themselves |
| **Flow B — Direct payment**  | `paymentProvider: "snap"` + `paymentMethod` + `channelId` | The Virtual Account number               | Stays inside your app; you show the VA number yourself           |

Pick **Flow A** when you want the fastest integration, no payment UI to build, and the widest choice of payment methods. Pick **Flow B** when you own the checkout experience end to end — mobile app, white-label web, kiosk — and would rather not send the customer to a page you don't control.

***

### Flow A — Hosted checkout

#### Request

```json
{
  "toBeMinted": "20000",
  "destinationWalletAddress": "0x7D1398C397C64368B7079bD5b3EE904aaaf8A495",
  "networkChainId": "8453",
  "returnUrl": "https://your-app.example.com/callback",
  "expiryPeriod": 60,
  "requestType": "idrx",
  "paymentProvider": "snap"
}
```

#### Response

```json
{
  "statusCode": 200,
  "message": "success",
  "data": {
    "id": 1234,
    "merchantOrderId": "20260728130000",
    "reference": "SNAP-20260728130000",
    "checkoutUrl": "https://checkout.idrx.co?token=eyJhbGciOi...",
    "paymentUrl": "https://checkout.idrx.co?token=eyJhbGciOi..."
  }
}
```

`checkoutUrl` and `paymentUrl` always carry the same value — redirect the customer to either one.

1. Redirect the user to `paymentUrl`.
2. User picks a payment method and pays.
3. User is redirected to your `returnUrl`.
4. You receive a callback on settlement.

{% hint style="info" %}
**Fee handling depends on the payment method the user picks.** For VA and e-wallet, the fee is added on top of `toBeMinted` (the user pays slightly more). Other channels deduct it from the minted IDRX instead. See [Fee structure by payment method](/api/callback.md#fee-structure-by-payment-method) for the full matrix.
{% endhint %}

{% hint style="info" %}
**QRIS is temporarily undocumented.** QRIS remains available on the hosted checkout page and is not being withdrawn — its documentation is hidden while we carry out maintenance, and will be restored afterwards. If you need QRIS details in the meantime, contact <support@idrx.co>.
{% endhint %}

{% hint style="warning" %}
Don't rely on the `returnUrl` redirect to confirm payment. Users close browsers. Always confirm via [callback](/api/callback.md) or [Transaction History](/api/transaction-api/get-api-transaction-user-transaction-history.md).
{% endhint %}

***

### Flow B — Direct payment

Set `paymentProvider: "snap"` together with `paymentMethod` and `channelId`. The response contains the generated **Virtual Account number** directly — no redirect, no hosted page. You render the payment instructions in your own UI.

#### Request

```json
{
  "toBeMinted": "20000",
  "destinationWalletAddress": "0x7D1398C397C64368B7079bD5b3EE904aaaf8A495",
  "networkChainId": "8453",
  "requestType": "idrx",
  "expiryPeriod": 60,
  "paymentProvider": "snap",
  "paymentMethod": "va",
  "channelId": "MANDIRI"
}
```

#### Response

```json
{
  "statusCode": 200,
  "message": "success",
  "data": {
    "id": 1234,
    "merchantOrderId": "20260728130000",
    "reference": "SNAP-20260728130000",
    "checkoutUrl": "https://checkout.idrx.co?token=eyJhbGciOi...",
    "paymentUrl": "https://checkout.idrx.co?token=eyJhbGciOi...",
    "paymentMethod": "va",
    "virtualAccountNo": "8680770000001234",
    "virtualAccountName": "JOHN SMITH",
    "amount": 24000,
    "baseAmount": 20000,
    "fees": [
      { "name": "VA Mandiri", "amount": 4000 }
    ],
    "expiredDate": "2026-07-28T14:00:00.000Z"
  }
}
```

#### Response fields

| Field                        | Type   | Description                                                                                                                                              |
| ---------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                         | number | Mint request ID.                                                                                                                                         |
| `merchantOrderId`            | string | Your order ID — use it for status polling and reconciliation.                                                                                            |
| `reference`                  | string | Internal payment reference, always `SNAP-<merchantOrderId>`.                                                                                             |
| `paymentMethod`              | string | Echoes the method you requested — `"va"`.                                                                                                                |
| `amount`                     | number | Total the customer must pay, in IDR. Equals `baseAmount` plus the sum of `fees`.                                                                         |
| `baseAmount`                 | number | The mint amount before channel fees.                                                                                                                     |
| `fees`                       | array  | Fee breakdown for this payment. `[]` when the channel charges no fee.                                                                                    |
| `fees[].name`                | string | Fee label.                                                                                                                                               |
| `fees[].amount`              | number | Fee amount in IDR.                                                                                                                                       |
| `expiredDate`                | string | ISO-8601 UTC timestamp when the Virtual Account stops accepting payment.                                                                                 |
| `virtualAccountNo`           | string | The account number the customer transfers to.                                                                                                            |
| `virtualAccountName`         | string | Account holder name shown in the customer's banking app.                                                                                                 |
| `checkoutUrl` / `paymentUrl` | string | The IDRX-hosted checkout page for this order. Not needed in Flow B — present so a single response shape works for both flows. Both carry the same value. |

{% hint style="warning" %}
**The customer must transfer the exact `amount`.** A Virtual Account accepts a differing amount, but the mismatch is flagged — paying less is treated as **underpaid**, paying more as **overpaid**. Either way the mint is held for manual review before IDRX is credited. Show the exact `amount` in your UI and discourage rounding.
{% endhint %}

#### Rendering the payload

Display `virtualAccountNo` as text, ideally with a copy button. The customer opens their banking app and transfers exactly `amount` to that number.

#### Supported channels

| `paymentMethod` | `channelId` | Bank         |
| --------------- | ----------- | ------------ |
| `"va"`          | `"MANDIRI"` | Bank Mandiri |
| `"va"`          | `"BRI"`     | Bank BRI     |

Any other `channelId` is rejected with `400`.

#### Errors specific to Flow B

| Status | Message                                                       | Cause                                        |
| ------ | ------------------------------------------------------------- | -------------------------------------------- |
| `400`  | `paymentMethod must be "va" or "qris" when set, got: <value>` | Unsupported `paymentMethod` — send `"va"`    |
| `400`  | `channelId is required when paymentMethod is set`             | `channelId` missing                          |
| `400`  | `Unsupported VA channel: <value>`                             | `channelId` is not enabled for your merchant |

{% hint style="info" %}
**Failed payload generation rolls the order back.** If the Virtual Account cannot be generated, the mint order created moments earlier is deleted before the error is returned. Nothing is left half-created — retry with a fresh request and you get a new `merchantOrderId`.
{% endhint %}

#### Retrieving the payment payload again

If your customer closes the app before paying and you no longer have the original response, fetch the cached payload for the same order instead of creating a new one:

```bash
curl -X GET 'https://idrx.co/api/duitku-snap/checkout/payment-payload?merchantOrderId=<MERCHANT_ORDER_ID>' \
  --header 'idrx-api-key: <API_KEY>' \
  --header 'idrx-api-sig: <SIGNATURE>' \
  --header 'idrx-api-ts: <TIMESTAMP>' \
  --header 'User-Agent: my-app/1.0'
```

The response returns the stored Virtual Account payload plus the current status:

| Field                                                | Description                                                                                                     |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `id`, `merchantOrderId`, `reference`                 | Order identifiers                                                                                               |
| `paymentStatus`, `userMintStatus`, `adminMintStatus` | Current state — see [Transaction History](/api/transaction-api/get-api-transaction-user-transaction-history.md) |
| `paymentAmount`, `toBeMinted`                        | Amounts                                                                                                         |
| `txHash`                                             | On-chain mint hash, once minted                                                                                 |
| `paymentMethod`                                      | `"va"`                                                                                                          |
| `virtualAccountNo`, `virtualAccountName`             | The Virtual Account details                                                                                     |
| `expiredDate`                                        | When the payment instrument expires                                                                             |

Only the partner that created the order can retrieve it.

{% hint style="info" %}
Re-requesting a payload for an order that already has one is safe — the same VA number is returned rather than a new one being generated.
{% endhint %}

***

### Confirming payment

Status tracking is identical in both flows — the fields, the callback, and the terminal states do not depend on how the payment was collected.

A transaction has two status fields you must track together:

| Field            | Tracks                                   |
| ---------------- | ---------------------------------------- |
| `paymentStatus`  | The fiat payment (VA or hosted checkout) |
| `userMintStatus` | The on-chain token delivery              |

#### Success state

```
paymentStatus: PAID
userMintStatus: MINTED
```

#### Terminal states

* `MINTED`
* `REFUND`
* `REJECTED`
* `paymentStatus: EXPIRED`

Confirm via [callback](/api/callback.md), or poll [Transaction History](/api/transaction-api/get-api-transaction-user-transaction-history.md) by `merchantOrderId` and stop on a terminal state.

> Full state machine, reconciliation pattern, and edge cases: [Processing Mint IDRX Requests](/integration/processing-mint-idrx-requests.md#step-2-track-the-status)

{% hint style="warning" %}
**Callbacks are sent once and not retried.** If your endpoint is down, you must reconcile by polling the Transaction History API. Always re-fetch the transaction before crediting users — treat the callback body as untrusted input.
{% endhint %}

***

### Errors

| Status | Cause                                                                                 | Fix                                                                                                                          |
| ------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid parameter                                                                     | Check param types and limits                                                                                                 |
| `400`  | `invalid toBeMinted: IDRX on chainId <id> supports <n> decimal place(s), got <value>` | The amount is finer than IDRX on that chain. Round it — whole numbers on Polygon and BNB Chain, at most 2 decimals elsewhere |
| `401`  | Bad signature or timestamp drift                                                      | Recompute signature; sync server clock (NTP)                                                                                 |
| `401`  | `User is not verified`                                                                | The account must complete verification before it can transact                                                                |
| `403`  | Blocked `User-Agent`                                                                  | Set custom `User-Agent` like `my-app/1.0`                                                                                    |
| `422`  | Unsupported `channelId` for merchant                                                  | Contact <support@idrx.co> to enable                                                                                          |
| `429`  | Rate limit                                                                            | Back off and retry                                                                                                           |
| `5xx`  | Server error                                                                          | Retry with backoff. Don't create a new order — recover the payload instead                                                   |

***

### See also

* [Callback](/api/callback.md) — webhook payload structure, fee breakdown per payment method, and reconciliation tips.
* [Processing Mint IDRX Requests](/integration/processing-mint-idrx-requests.md) — end-to-end mint workflow with status state machine and edge cases.
* [Transaction History API](/api/transaction-api/get-api-transaction-user-transaction-history.md) — query transaction state by `merchantOrderId`.
* [Generating a Signature](/api/generating-a-signature.md) — HMAC-SHA256 signature reference with code examples.

***

The amount the customer pays is not the same as `toBeMinted` — fees are applied on top of it, or deducted from the minted IDRX, depending on the payment method. In Flow A the final figure is shown on the checkout page; in Flow B it comes back as `amount` in the response.

Shortly after the payment is done, IDRX tokens will be minted and sent to the specified destination wallet address. In the case of "usdt" `requestType`, USDT tokens will be sent to the wallet address.

To check the status of the transaction, you can use the [Transaction History API](/api/transaction-api/get-api-transaction-user-transaction-history.md).
