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

# Escrow

> Hold funds between two Monei users until a delivery, booking, or milestone is confirmed

Escrow lets your app hold money from one Monei user until a condition is met, then release it to the recipient, on your own schedule and your own confirmation logic. Common uses: marketplace orders, service bookings, milestone-based payments, anything where a buyer and seller need trust before money changes hands.

<Info>
  Escrow is a native Monei feature. It works for users acting directly through the Monei app, and for third-party apps via Monei Connect. This page covers the Connect side.
</Info>

***

## How it works

Escrow uses a **hold model**, not a transfer model. When you place a hold, the payer's funds aren't moved anywhere, they're simply marked unavailable to spend. Nothing physically leaves their wallet until you release it.

<Steps>
  <Step title="Hold funds">
    Call `escrow:create` with a payer, payee, and amount. The payer's available balance drops by that amount immediately. Their total balance doesn't change.
  </Step>

  <Step title="Payer confirms fulfilment">
    Your app decides when the condition is met, order delivered, service completed, milestone reached. There's no built-in timer or webhook for this: you own that logic entirely.
  </Step>

  <Step title="Release or refund">
    Release moves the held amount to the payee and closes out the hold. Refund releases the hold back to the payer without moving any money, since nothing was ever moved in the first place.
  </Step>
</Steps>

This means refunds are instant and free, there's no reversed transfer to wait on. It also means a held amount never sits in a pooled or intermediary account, it stays attached to the payer's own wallet the entire time it's held.

***

## Scopes

| Scope            | What it allows                    | Sensitive |
| ---------------- | --------------------------------- | --------- |
| `escrow:create`  | Hold funds from the user's wallet | Yes       |
| `escrow:release` | Release held funds to the payee   | Yes       |
| `escrow:refund`  | Return held funds to the payer    | Yes       |
| `escrow:dispute` | Flag a held escrow as disputed    | Yes       |
| `escrow:read`    | View escrow transaction status    | No        |

<Info>
  Dispute **resolution** is not exposed via Connect. Once an escrow is disputed, only a Monei admin can resolve it. Your app can flag a dispute, but the outcome is decided on Monei's side. See [Disputes](#disputes) below.
</Info>

***

## Platform fees

If your app takes a cut on transactions it facilitates, pass `platformFeeAmount` when you place the hold. The fee is deducted from the **payee's** payout at release time, the payer is never charged more than the amount you hold.

<Info>
  Your app needs a fee recipient wallet configured before you can hold funds with a nonzero `platformFeeAmount`. Contact [support@monei.cc](mailto:support@monei.cc) to set this up, holds with an unconfigured fee recipient are rejected before any funds are touched.
</Info>

***

## Endpoints

### `escrow:create`

```
POST /api/v1/escrow/hold
```

```json theme={null}
{
  "payeeId": "f4468d5f-b7dc-446f-80d4-47e3c00b6bcd",
  "amount": 5000,
  "platformFeeAmount": 250,
  "holdReference": "order-8842",
  "metadata": { "orderId": "8842" }
}
```

`holdReference` is your idempotency key. Retrying a hold with the same reference returns the original hold instead of creating a second one, safe to retry on timeouts or network errors.

`platformFeeAmount` is optional and only valid alongside a configured fee recipient. Omit it entirely if your app doesn't take a cut.

For milestone-based payments, add `depositAmount` to release a portion immediately while the rest stays held:

```json theme={null}
{
  "payeeId": "f4468d5f-b7dc-446f-80d4-47e3c00b6bcd",
  "amount": 50000,
  "depositAmount": 20000,
  "holdReference": "booking-330"
}
```

The response status will be `PARTIAL` rather than `HELD` when a deposit has already gone out.

***

### `escrow:release`

```
POST /api/v1/escrow/{id}/release
```

```json theme={null}
{}
```

Releasing with an empty body releases everything still held. To release part of a milestone escrow, pass an amount:

```json theme={null}
{ "amount": 15000 }
```

Only the **payer** can call this endpoint for a given escrow, your app should trigger this after your own confirmation logic decides the payer's side of the deal is done, not the payee's.

***

### `escrow:refund`

```
POST /api/v1/escrow/{id}/refund
```

```json theme={null}
{}
```

Refunds everything still held back to the payer. Only the **payee** can call this endpoint, they're the one giving up their claim to the funds.

***

### `escrow:dispute`

```
POST /api/v1/escrow/{id}/dispute
```

```json theme={null}
{ "reason": "Order not delivered" }
```

Either party can flag a dispute. Once disputed, `release` and `refund` are both frozen, they'll return `403 Forbidden` until a Monei admin resolves the dispute.

***

### `escrow:read`

```
GET /api/v1/escrow/{id}
GET /api/v1/escrow/mine/paying
GET /api/v1/escrow/mine/receiving
GET /api/v1/escrow/mine/pending-incoming
```

`mine/paying` and `mine/receiving` list the user's escrow history from each side. `mine/pending-incoming` returns a single figure, the total still held and committed to the user as a payee, useful for showing "funds on the way" before they're spendable.

***

## Escrow status values

| Status     | Meaning                                                               |
| ---------- | --------------------------------------------------------------------- |
| `HELD`     | Funds are held, nothing released yet                                  |
| `PARTIAL`  | Some funds released (e.g. a milestone deposit), remainder still held  |
| `DISPUTED` | Frozen. `release` and `refund` are blocked until an admin resolves it |
| `RELEASED` | Fully released to the payee                                           |
| `REFUNDED` | Fully returned to the payer                                           |

***

## Disputes

Dispute resolution is intentionally kept off Connect. Your app can raise a dispute with `escrow:dispute`, but the actual outcome, release or refund, is decided by a Monei admin, not by either party or by your app's backend. This is by design: allowing either side (or an integrator acting on their behalf) to resolve their own dispute would defeat the point of escrow.

If your product needs its own dispute-handling workflow (e.g. a support team reviewing evidence before deciding), build that on your side and contact Monei with the outcome you've reached. Automated dispute resolution via Connect may be added in a future version.

***

## Example: order flow

<CodeGroup>
  ```javascript Node.js theme={null}
  const MONEI_BASE = 'https://api.monei.cc/api/v1';

  // Hold funds when the order is placed
  const hold = await fetch(`${MONEI_BASE}/escrow/hold`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      payeeId: merchant.moneiUserId,
      amount: order.total,
      platformFeeAmount: order.platformFee,
      holdReference: `order-${order.id}`,
      metadata: { orderId: order.id },
    }),
  });

  const escrow = await hold.json();

  // Later, once the buyer confirms delivery
  await fetch(`${MONEI_BASE}/escrow/${escrow.id}/release`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${buyerAccessToken}`,
    },
    body: JSON.stringify({}),
  });
  ```

  ```python Python theme={null}
  import requests

  MONEI_BASE = "https://api.monei.cc/api/v1"

  # Hold funds when the order is placed
  hold_res = requests.post(
      f"{MONEI_BASE}/escrow/hold",
      headers={"Authorization": f"Bearer {access_token}"},
      json={
          "payeeId": merchant_monei_user_id,
          "amount": order_total,
          "platformFeeAmount": order_platform_fee,
          "holdReference": f"order-{order_id}",
          "metadata": {"orderId": order_id},
      },
  )

  escrow = hold_res.json()

  # Later, once the buyer confirms delivery
  requests.post(
      f"{MONEI_BASE}/escrow/{escrow['id']}/release",
      headers={"Authorization": f"Bearer {buyer_access_token}"},
      json={},
  )
  ```
</CodeGroup>

Note the release call uses the **buyer's** access token, not the merchant's, since only the payer can trigger a release.

***

<CardGroup cols={2}>
  <Card title="Scopes Reference" icon="key" href="/connect/scopes">
    See how escrow scopes fit alongside the rest of Monei Connect
  </Card>

  <Card title="Errors & Rate Limits" icon="triangle-exclamation" href="/connect/errors">
    What a blocked or unauthorized escrow call looks like
  </Card>
</CardGroup>
