> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinmarkt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant payouts

> Read Join Markt Crypto balance and create or cancel payout requests with a Bearer API key.

All routes require:

```txt theme={null}
Authorization: Bearer <your_key>
```

Paths are store-scoped. The key must include the scope for that method and access to that shop. Dashboard permission ceiling: `managePayments`.

`storeId` in the JSON body is ignored. The shop is the `{storeId}` in the path.

These routes are rate limited to 120 requests per 60 seconds (`429`).

There is no list method on `/payouts`. Recent payout requests are on the balance response. Create is `POST /payouts`. Cancel is `DELETE /payouts/{requestId}`.

## Get balance

```http theme={null}
GET /api/merchant/v1/stores/{storeId}/balance
```

Scope: `balance:read`

Status `200`. A missing store returns `404`. A missing store id in the path is `400` (`Store ID is required`).

```json theme={null}
{
  "store": {
    "id": "cm...",
    "name": "Shop",
    "subdomain": "shop",
    "currency": "USD",
    "balance": 120.5
  },
  "gateway": {
    "enabled": true,
    "lifetimeCharged": 400,
    "lifetimeSellerCredited": 360,
    "lifetimeCommission": 40,
    "paidOrderCount": 12
  },
  "payout": {
    "enabled": true,
    "email": "owner@example.com",
    "wallets": { "usdtTron": "T...", "btc": null, "ltc": null },
    "ready": true,
    "minAmount": 10
  },
  "recentPayments": [],
  "recentPayoutRequests": []
}
```

| Field | Meaning |
| - | - |
| `store.currency` | Store currency, first 3 characters, uppercased. Empty becomes `USD` |
| `store.balance` | Available balance. Missing becomes `0` |
| `gateway.enabled` | `joinMarktCryptoEnabled` |
| `gateway.lifetimeCharged` | Sum of `price` for counted orders. Missing sum is `0` |
| `gateway.lifetimeSellerCredited` | Sum of `sellerAmount`. Missing sum is `0` |
| `gateway.lifetimeCommission` | Sum of `commissionAmount`. Missing sum is `0` |
| `gateway.paidOrderCount` | Count of those orders |
| `payout.enabled` | `joinMarktCryptoPayoutEnabled` |
| `payout.email` | `joinMarktCryptoPayoutEmail`, or null |
| `payout.wallets` | `{ usdtTron, btc, ltc }`, each a string or null |
| `payout.ready` | Payouts enabled and at least one wallet is set |
| `payout.minAmount` | Platform `joinMarktCryptoPayoutMinAmount` when it is a finite number greater than 0, rounded to cents. Otherwise `10` |

Lifetime totals and `recentPayments` include orders that have a Join Markt Crypto invoice id and status `paid` or `completed`.

`recentPayments` is at most 25 rows, ordered by `completedAt` descending, then `createdAt` descending.

| Field | Meaning |
| - | - |
| `orderId` | Public order id |
| `productName` | |
| `status` | Order status |
| `chargedTotal` | Order `price` |
| `sellerAmount` | Missing becomes `0` |
| `commissionAmount` | Missing becomes `0` |
| `paymentMethod` | Order payment method string |
| `asset` | Payer currency, or null |
| `network` | Payer network, or null |
| `completedAt` | ISO timestamp, or null |
| `createdAt` | ISO timestamp |

`recentPayoutRequests` is at most 25 rows, newest `createdAt` first. Cancel only finds a request that is in this list.

| Field | Meaning |
| - | - |
| `id` | Internal request id |
| `amount` | |
| `currency` | |
| `status` | `pending`, `processing`, `paid`, `rejected`, or `cancelled` |
| `asset` | `USDT`, `BTC`, or `LTC` |
| `network` | `tron`, `bitcoin`, or `litecoin`, or null |
| `walletAddress` | |
| `payoutEmail` | String or null |
| `note` | String or null |
| `adminNote` | String or null |
| `createdAt` | ISO timestamp |
| `processedAt` | ISO timestamp, or null |

The response does not include `publicId`.

## Create payout

```http theme={null}
POST /api/merchant/v1/stores/{storeId}/payouts
```

Scope: `payout:create`

Status `201`.

```json theme={null}
{ "amount": 50, "walletKey": "usdtTron", "note": "Weekly payout" }
```

| Field | Rule |
| - | - |
| `amount` | Finite number greater than 0. Numeric strings are coerced. Rounded to cents before the minimum and balance checks |
| `walletKey` | `usdtTron`, `btc`, or `ltc` |
| `note` | Optional. String max 500 after trim, or null. Blank becomes null |

`walletKey` selects a wallet already stored on the shop:

| `walletKey` | `asset` | `network` |
| - | - | - |
| `usdtTron` | `USDT` | `tron` |
| `btc` | `BTC` | `bitcoin` |
| `ltc` | `LTC` | `litecoin` |

Payouts must be enabled and at least one wallet must be set. The selected wallet must be non-empty. `amount` must be at least `payout.minAmount` and no greater than `store.balance`. Currency on the request is the store currency (same 3-letter rule as balance). `payoutEmail` on the created row is the store payout email.

```json theme={null}
{
  "request": {
    "id": "cm...",
    "amount": 50,
    "currency": "USD",
    "status": "pending",
    "asset": "USDT",
    "network": "tron",
    "walletAddress": "T...",
    "payoutEmail": "owner@example.com",
    "note": "Weekly payout",
    "adminNote": null,
    "createdAt": "2026-10-25T12:00:00.000Z",
    "processedAt": null
  }
}
```

`request` uses those fields only. `status` starts as `pending`.

| `code` | When |
| - | - |
| `payout_min_amount` | Amount is below the minimum. Payload includes `minAmount`. Error text is `Minimum payout is $X.XX` |
| `payout_not_ready` | Payouts are off or no wallet is configured |
| `wallet_missing` | The selected wallet is empty |
| `insufficient_balance` | Balance is below the amount. Payload includes `balance` |

Invalid body is `400` with the field errors joined. Store not found is `404`.

## Cancel payout

```http theme={null}
DELETE /api/merchant/v1/stores/{storeId}/payouts/{requestId}
```

Scope: `payout:create`

`{requestId}` is the internal `id`, or the public id `PAY-XXXX-XXXX-XXXX-XXXX-XXXX`. A public id is resolved to the internal id for this shop. An unknown public id is left as sent and then fails the lookup below.

The request must appear in `recentPayoutRequests` (the latest 25). Otherwise the response is `404` (`Payout request not found`), including when an older request still exists.

Only `pending` can be cancelled. Another status returns `400` (`Only pending payout requests can be cancelled`, `code` `not_cancellable`).

Success is `200`:

```json theme={null}
{ "ok": true, "amount": 50 }
```

`amount` is credited back to the store balance. The cancelled row is not in this response. Read balance again to see the new balance.

## Errors

| Status | Meaning |
| - | - |
| 400 | Invalid JSON or body, minimum, wallet, balance, or a non-pending cancel |
| 401 | Missing or invalid Bearer token |
| 403 | Scope or shop not allowed |
| 404 | Store or payout request not found |
| 429 | Rate limit exceeded |

Error JSON is `{ "success": false, "error": "...", "code": "..." }` plus any payload fields from the table above.

## Related

* [Merchant payments](https://docs.joinmarkt.com/api-reference/merchant-payments.md)
* [API keys](https://docs.joinmarkt.com/developers/api-keys.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.