> For the complete documentation index, see [llms.txt](https://apc-pay.gitbook.io/apc-pay-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://apc-pay.gitbook.io/apc-pay-docs/apc-pay/create-a-payout.md).

# Create a Payout

Updated 2026-10-14

Create an outbound fiat payout funded from your merchant USDT balance.

> ❇️
>
> ### Real-money operation
>
> The supplied materials do not document a sandbox. Treat this endpoint as a real-money operation unless APC Pay has explicitly provided you with a test environment.

## Endpoint

```http
POST /payout/create
Content-Type: application/json; charset=utf-8
```

Send the request to:

```
https://pwc-pay-api.chiachair.com/v1/payout/create
```

Sign it using the backend path `/payout/create`. See [Authentication and Request Signing](broken://pages/09fdb741f99cf1deb38cf1b90b063767f12807df).

## Preflight checks

Before creating an order:

1. Follow [Balance and Currency](broken://pages/c374e00afa6fb109644d34b6d82fbe7b0d8b38c7) and call `POST /currency/query` for the requested fiat currency.
2. Confirm the currency is enabled and the amount is between `minAmount` and `maxAmount`.
3. Format the amount with exactly the returned `decimals`.
4. Confirm the amount and currency agree with the complete payee QR payload.
5. Call `POST /balance/query` and confirm sufficient usable USDT.
6. Confirm that the callback URL is public HTTPS and ready to receive signed notifications.
7. Allocate a new, unique `mchOrderNo`.

### Rate and fee calculation

APC Pay calculates the locked USDT cost as:

```
adjustedExchangeRate = exchangeRate / (1 + feeRate)
usdtAmount = fiatAmount / adjustedExchangeRate + fixedFee / exchangeRate
```

The result is rounded up to 18 USDT decimal places. Rates and fees are locked when the order is created.

## Request fields

| Field          |   Type | Required | Description                                                                      |
| -------------- | -----: | :------: | -------------------------------------------------------------------------------- |
| `mchOrderNo`   | string |    Yes   | Unique merchant order number. Use 1-64 letters, digits, underscores, or hyphens. |
| `fiatAmount`   | string |    Yes   | Fiat amount with exactly the currency's required decimal places.                 |
| `fiatCurrency` | string |    Yes   | ISO 4217 fiat currency code enabled for the app.                                 |
| `qrContent`    | string |    Yes   | Complete payee QR code content.                                                  |
| `userEmail`    | string |    Yes   | Payee email registered with the payout channel.                                  |
| `notifyUrl`    | string |    Yes   | Public HTTPS callback URL.                                                       |
| `extParam`     | string |    No    | Your extension value. It is returned in queries and callbacks.                   |

The common `mchNo`, `appId`, `reqTime`, `nonceStr`, and `sign` fields are also required.

## Request example

The signature is a placeholder. Generate a new timestamp, nonce, and signature for every request.

```json
{
  "mchNo": "your-merchant-number",
  "appId": "your-app-id",
  "reqTime": "2026-10-14T08:30:00.000Z",
  "nonceStr": "0123456789abcdef0123456789abcdef",
  "mchOrderNo": "PO-20261014-0001",
  "fiatAmount": "100.00",
  "fiatCurrency": "PHP",
  "qrContent": "complete-payee-qr-payload",
  "userEmail": "payee@example.com",
  "notifyUrl": "https://merchant.example.com/webhooks/apc-pay",
  "extParam": "customer-withdrawal-8675309",
  "sign": "base64-rsa-sha256-signature"
}
```

## Successful response

`code: 0` means success. A new payout starts in `ACCEPTED`.

```json
{
  "code": 0,
  "msg": "success-message",
  "requestId": "platform-request-id",
  "data": {
    "payoutOrderId": "platform-payout-order-id",
    "mchOrderNo": "PO-20261014-0001",
    "status": "ACCEPTED",
    "fiatAmount": "100.00",
    "fiatCurrency": "PHP",
    "exchangeRate": "locked-market-rate",
    "feeRate": "locked-fee-rate",
    "fixedFee": "locked-fixed-fee",
    "adjustedExchangeRate": "locked-adjusted-rate",
    "usdtAmount": "locked-usdt-cost",
    "bankCode": "decoded-bank-code",
    "bankName": "decoded-bank-name",
    "bankAccountNoMasked": "masked-account-number",
    "bankAccountName": "payee-name",
    "nonceStr": "0123456789abcdef0123456789abcdef"
  },
  "sign": "platform-base64-signature"
}
```

Before accepting the response, verify the platform signature over `data`, check that `data.nonceStr` matches the request, and confirm the order identifiers.

Store at least:

* `mchOrderNo` and `payoutOrderId`;
* the initial `status`;
* amount and currency;
* all locked rate, fee, and USDT values; and
* `requestId` for support investigations.

A non-zero `code` means failure. Failed responses contain an empty `data` object and do not carry a successful-data signature.

## Idempotency and safe retries

`mchOrderNo` is the payout idempotency key.

* The same `mchOrderNo` with the same business parameters returns the same order.
* The same `mchOrderNo` with different business parameters returns `3002 PAYOUT_NO_DUPLICATED`.
* `3001 INSUFFICIENT_BALANCE` and `3010 AMOUNT_OUT_OF_RANGE` do not create an order. Correct the problem and retry with the same `mchOrderNo`.
* After a timeout, transport error, or `4001 SYSTEM_ERROR`, query using the same `mchOrderNo` before retrying create.
* Every retry requires a fresh `reqTime`, a fresh `nonceStr`, and a newly calculated signature.

```
Create request
  ├─ Confirmed success → store returned order
  ├─ Confirmed business error with no order → correct and retry safely
  └─ Timeout / transport error / 4001
       └─ Query by mchOrderNo
            ├─ Order found → continue tracking that order
            └─ Order not found → retry create with the same business data,
                                 fresh time, fresh nonce, and fresh signature
```

Never create a replacement payout merely because the original request timed out.

## Next step

Continue to [Query a Payout](broken://pages/d322bd86cc349f7eef7630c43fcea7a59ed35e89).
