<!-- ZenPays documentation · https://docs.zenpayz.com/docs/rest-api/endpoints/ramp/payout-preview -->

# Get Payout Preview (Phase 2a)

Before submitting a sell payout, call this endpoint to discover which beneficiary fields are required for the destination currency and country. The response contains dynamic form field definitions that your frontend should render for the customer to fill in.

<EndpointHeader verb="GET" path="/payment/api/v1/off-ramp/sell/payout-preview" />

## Request

### Headers

<ParamTable
  label="Header"
  rows={[
    { name: "x-request-id", desc: "Custom request ID for tracing" },
  ]}
/>

:::note Public Endpoint
This endpoint does not require authentication headers.
:::

### Query Parameters

<ParamTable
  rows={[
    { name: "intentId", type: "string", required: true, default: "—", desc: "Ramp intent ID" },
    { name: "fiatCurrency", type: "string", required: true, default: "—", desc: "Target fiat currency (e.g. `USD`)" },
    { name: "country", type: "string", default: "US", desc: "ISO country code for the payout destination" },
    { name: "payoutType", type: "string", default: "bank_transfer", desc: "Payout method" },
  ]}
/>

:::caution Prerequisite
The crypto deposit for this intent must be in `confirmed` or `matched` status. If the deposit hasn't been confirmed yet, this endpoint returns a `400` error.
:::

## Response

### Success (200 OK)

```json
{
  "success": true,
  "data": {
    "selectedTsp": "zenpay_routing",
    "tspDisplayName": "ZenPays",
    "requiredFields": [
      {
        "fieldName": "receiverFirstName",
        "label": "First Name",
        "type": "text",
        "required": true,
        "placeholder": "Enter first name"
      },
      {
        "fieldName": "receiverLastName",
        "label": "Last Name",
        "type": "text",
        "required": true,
        "placeholder": "Enter last name"
      },
      {
        "fieldName": "receiverAccountNumber",
        "label": "Account Number",
        "type": "text",
        "required": true,
        "placeholder": "Enter account number"
      },
      {
        "fieldName": "receiverCountry",
        "label": "Country",
        "type": "text",
        "required": true,
        "placeholder": "ISO country code (e.g. US)"
      },
      {
        "fieldName": "receiverBankName",
        "label": "Bank Name",
        "type": "text",
        "required": true,
        "placeholder": "Enter bank name"
      }
    ],
    "optionalFields": [
      {
        "fieldName": "receiverEmail",
        "label": "Email",
        "type": "email",
        "required": false,
        "placeholder": "Enter email address"
      },
      {
        "fieldName": "receiverPhone",
        "label": "Phone",
        "type": "tel",
        "required": false,
        "placeholder": "Enter phone number"
      }
    ],
    "oneOfGroups": [
      ["receiverBankCode", "receiverBankName"]
    ],
    "fiatAmount": 19.78,
    "fiatCurrency": "USD",
    "fees": {
      "platform": 0.62,
      "network": 0,
      "total": 0.62
    }
  },
  "message": "Payout preview retrieved"
}
```

### Response Fields

<ParamTable
  rows={[
    { name: "selectedTsp", type: "string", desc: "Selected payout provider" },
    { name: "tspDisplayName", type: "string", desc: "Human-readable provider name" },
    { name: "requiredFields", type: "array", desc: "Fields that must be provided" },
    { name: "optionalFields", type: "array", desc: "Fields that can optionally be provided" },
    { name: "oneOfGroups", type: "array | null", desc: "Groups where at least one field must be filled" },
    { name: "fiatAmount", type: "number", desc: "Calculated fiat payout amount" },
    { name: "fiatCurrency", type: "string", desc: "Fiat currency" },
    { name: "fees", type: "object", desc: "Fee breakdown" },
  ]}
/>

### Field Definition Object

<ParamTable
  rows={[
    { name: "fieldName", type: "string", desc: "Key to use in `beneficiaryDetails` when submitting payout" },
    { name: "label", type: "string", desc: "Human-readable label for the form field" },
    { name: "type", type: "string", desc: "Input type: `text`, `email`, `tel`, `number`, or `select`" },
    { name: "required", type: "boolean", desc: "Whether the field is mandatory" },
    { name: "placeholder", type: "string", desc: "Placeholder text" },
    { name: "pattern", type: "string | null", desc: "Regex validation pattern" },
    { name: "validationMessage", type: "string | null", desc: "Error message for failed validation" },
    { name: "options", type: "array | null", desc: "Options for `select` type fields" },
    { name: "minLength", type: "number | null", desc: "Minimum character length" },
    { name: "maxLength", type: "number | null", desc: "Maximum character length" },
  ]}
/>

### Fees Object

<ParamTable
  rows={[
    { name: "platform", type: "number", desc: "Platform fee" },
    { name: "network", type: "number", desc: "Network/gas fee" },
    { name: "total", type: "number", desc: "Total fees" },
  ]}
/>

:::tip Dynamic Form Rendering
Use the `requiredFields` and `optionalFields` arrays to dynamically build your payout form. Each field includes validation constraints (`pattern`, `minLength`, `maxLength`) so you can validate client-side before submitting.

For `oneOfGroups`, at least one field from each group must be provided. For example, `["receiverBankCode", "receiverBankName"]` means you need either the SWIFT/BIC code or the bank name.
:::

### Error Responses

<ErrorTable
  rows={[
    { code: "BAD_REQUEST", status: "400", message: "Deposit not yet confirmed, or invalid intent" },
    { code: "NOT_FOUND", status: "404", message: "Intent not found" },
    { code: "SELL_PAYOUT_PREVIEW_FAILED", status: "500", message: "Failed to generate preview" },
  ]}
/>

<CodeRail>

## Examples

<Tabs groupId="language">
  <TabItem value="curl" label="cURL" default>

```bash
curl "https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout-preview?intentId=ri_1710345678000_a1b2c3d4e5f6g7h8&fiatCurrency=USD&country=US"
```

  </TabItem>
  <TabItem value="javascript" label="JavaScript">

```javascript
const params = new URLSearchParams({
  intentId: 'ri_1710345678000_a1b2c3d4e5f6g7h8',
  fiatCurrency: 'USD',
  country: 'US',
});

const response = await fetch(
  `https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout-preview?${params}`
);

const { data } = await response.json();

console.log(`Provider: ${data.tspDisplayName}`);
console.log(`Payout: ${data.fiatAmount} ${data.fiatCurrency}`);
console.log(`Fees: ${data.fees.total}`);

// Dynamically render form fields
data.requiredFields.forEach((field) => {
  const input = document.createElement('input');
  input.name = field.fieldName;
  input.placeholder = field.placeholder;
  input.type = field.type;
  input.required = field.required;
  if (field.pattern) input.pattern = field.pattern;
  document.getElementById('payout-form').appendChild(input);
});
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import requests

response = requests.get(
    "https://api.zenpayz.com/payment/api/v1/off-ramp/sell/payout-preview",
    params={
        "intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
        "fiatCurrency": "USD",
        "country": "US",
    },
)

data = response.json()["data"]
print(f"Provider: {data['tspDisplayName']}")
print(f"Payout: {data['fiatAmount']} {data['fiatCurrency']}")
print(f"Fees: {data['fees']['total']}")

print("\nRequired fields:")
for field in data["requiredFields"]:
    print(f"  - {field['label']} ({field['fieldName']}): {field['type']}")
```

  </TabItem>
</Tabs>

</CodeRail>

## Next Steps

- [Submit Sell Payout](/docs/rest-api/endpoints/ramp/sell-payout) — Submit beneficiary details and trigger payout (Phase 2b)
- [Create Sell Deposit](/docs/rest-api/endpoints/ramp/sell-deposit) — Generate deposit address (Phase 1)
- [Off-Ramp Flow](/docs/examples/off-ramp-flow) — Complete sell integration guide
