<!-- ZenPays documentation · https://docs.zenpayz.com/docs/rest-api/endpoints/refunds/bulk-upload -->

# Bulk Upload Refunds

Upload multiple refunds in a single API call. Supports all the same beneficiary fields as the [Confirm Refund Intent](/docs/rest-api/endpoints/refunds/create-refund) endpoint. Use [Create Refund Intent](/docs/rest-api/endpoints/refunds/get-template) to discover the required fields first.

<EndpointHeader verb="POST" path="/merchant/api/v1/refund-intents/bulk-upload" />

## Request

### Headers

<ParamTable
  label="Header"
  rows={[
    { name: "Authorization", required: true, desc: "`Bearer {api_key}`" },
    { name: "X-Signature", required: true, desc: "HMAC-SHA256 signature" },
    { name: "X-Timestamp", required: true, desc: "ISO 8601 timestamp" },
    { name: "X-Secret-Salt", required: true, desc: "Secret salt for HMAC validation" },
    { name: "Content-Type", required: true, desc: "`application/json`" },
  ]}
/>

### Body Parameters

<ParamTable
  rows={[
    {
      name: "refunds",
      type: "array",
      required: true,
      desc: "Array of refund requests",
      nested: [
        { name: "refunds[].transactionId", type: "string", required: true, desc: "Original transaction ID" },
        { name: "refunds[].amount", type: "number", desc: "Refund amount. Omit for full refund." },
        { name: "refunds[].reason", type: "string", desc: "Reason for refund" },
        { name: "refunds[].customerId", type: "string", desc: "Customer ID (resolved from transaction if omitted)" },
        { name: "refunds[].beneficiaryVpa", type: "string", desc: "UPI VPA for UPI refunds" },
        { name: "refunds[].beneficiaryAccount", type: "string", desc: "Bank account number" },
        { name: "refunds[].beneficiaryIfsc", type: "string", desc: "IFSC code (India)" },
        { name: "refunds[].beneficiaryName", type: "string", desc: "Beneficiary name" },
        { name: "refunds[].beneficiaryMobile", type: "string", desc: "Mobile number" },
        { name: "refunds[].beneficiaryEmail", type: "string", desc: "Beneficiary email address" },
        { name: "refunds[].beneficiaryCity", type: "string", desc: "Beneficiary city" },
        { name: "refunds[].beneficiaryWalletAddress", type: "string", desc: "Crypto wallet address (crypto refunds)" },
        { name: "refunds[].beneficiaryWalletMemo", type: "string", desc: "Wallet memo/tag for XRP/XLM" },
        { name: "refunds[].chain", type: "string", desc: "Blockchain chain (ethereum, tron, bitcoin)" },
      ],
    },
  ]}
/>

:::tip Which beneficiary fields do I need?
Call [Create Refund Intent](/docs/rest-api/endpoints/refunds/get-template) for any transaction to discover the required fields. You can also use the template response to generate a CSV file for bulk uploads — the `fieldName` values in `requiredFields`, `optionalFields`, and `oneOfGroups` map directly to column headers.
:::

## Response

### Success (201 Created)

```json
{
  "success": true,
  "data": {
    "successful": [
      {
        "refundId": "refund_1774683026458_oann5ibjf",
        "merchantId": "ZP_FIN_1774592804_0781001264",
        "customerId": "cust_mn8yfewm_37ntht",
        "transactionId": "txn_1774619159530_1_mn8yfmph_5iwb9b",
        "amount": 30,
        "originalAmount": 100,
        "currency": "INR",
        "refundType": "partial",
        "status": "pending_approval",
        "tspProvider": "sulifu_pay",
        "reason": "Customer requested refund",
        "createdAt": "2026-03-28T07:30:26.479Z"
      }
    ],
    "failed": [
      {
        "transactionId": "txn_invalid",
        "error": "Transaction not found"
      }
    ],
    "totalRequested": 2,
    "totalSuccessful": 1,
    "totalFailed": 1
  }
}
```

### Requires Beneficiary Details

If any refund in the batch requires beneficiary details that were not provided, that item will appear in the `successful` array with `requiresBeneficiary: true` and field definitions. Re-submit that item with the required fields.

```json
{
  "requiresBeneficiary": true,
  "status": "requires_beneficiary",
  "tspProvider": "sulifu_pay",
  "currency": "INR",
  "transactionId": "txn_xxxxx",
  "requiredFields": [...],
  "optionalFields": [...],
  "oneOfGroups": [["beneficiaryVpa"], ["beneficiaryAccount", "beneficiaryIfsc"]]
}
```

:::tip Avoid requires_beneficiary responses
To avoid this, call [Create Refund Intent](/docs/rest-api/endpoints/refunds/get-template) first for a sample transaction and include the required beneficiary fields in every batch item.
:::

## CSV Upload Workflow

For bulk refunds, you can use a CSV file workflow:

1. **Create refund intent**: Call `POST /refund-intents` with a sample `transactionId` to discover required fields
2. **Build CSV**: Use `fieldName` values from `requiredFields`, `optionalFields`, and `oneOfGroups` as column headers, alongside base columns (`transactionId`, `amount`, `reason`)
3. **Fill CSV**: Populate rows with refund data — one row per refund
4. **Submit**: Parse the CSV and POST the rows as a JSON array to `POST /refund-intents/bulk-upload`

### Example CSV

```csv
transactionId,amount,reason,beneficiaryEmail,beneficiaryVpa,beneficiaryAccount,beneficiaryIfsc,beneficiaryName,beneficiaryMobile,beneficiaryCity
txn_aaa,20,Customer request,john@example.com,john@upi,,,John Doe,+919876543210,Mumbai
txn_bbb,500,Duplicate charge,alice@example.com,,1234567890,HDFC0001234,Alice Smith,+919123456789,Delhi
txn_ccc,250,Item not received,bob@example.com,bob@ybl,,,Bob Johnson,+918765432109,Bangalore
```

:::info Column headers must match field names exactly
The CSV column headers must match the `fieldName` values from the template response (e.g., `beneficiaryVpa`, `beneficiaryAccount`, `beneficiaryIfsc`). Leave columns empty for fields you don't need — for example, if using UPI (VPA), leave `beneficiaryAccount` and `beneficiaryIfsc` empty.
:::

<CodeRail>

## Examples

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

```bash
curl -X POST https://api.zenpayz.com/merchant/api/v1/refund-intents/bulk-upload \
  -H "Authorization: Bearer zp_test_xxxxx" \
  -H "X-Timestamp: 2024-01-15T10:30:00.000Z" \
  -H "X-Signature: a1b2c3d4e5f6..." \
  -H "X-Secret-Salt: your_secret_salt" \
  -H "Content-Type: application/json" \
  -d '{
    "refunds": [
      {
        "transactionId": "txn_aaaaa",
        "amount": 500,
        "reason": "Customer request",
        "beneficiaryEmail": "john@example.com",
        "beneficiaryVpa": "john@upi",
        "beneficiaryName": "John Doe",
        "beneficiaryMobile": "+919876543210"
      },
      {
        "transactionId": "txn_bbbbb",
        "amount": 1000,
        "reason": "Duplicate charge",
        "beneficiaryEmail": "alice@example.com",
        "beneficiaryAccount": "1234567890",
        "beneficiaryIfsc": "HDFC0001234",
        "beneficiaryName": "Alice Smith"
      }
    ]
  }'
```

  </TabItem>
  <TabItem value="sdk" label="SDK (Recommended)">

```typescript
const result = await zenpays.refunds.bulkUpload({
  refunds: [
    {
      transactionId: 'txn_aaaaa',
      amount: 500,
      reason: 'Customer request',
      beneficiaryEmail: 'john@example.com',
      beneficiaryVpa: 'john@upi',
      beneficiaryName: 'John Doe',
    },
    {
      transactionId: 'txn_bbbbb',
      amount: 1000,
      reason: 'Duplicate charge',
      beneficiaryEmail: 'alice@example.com',
      beneficiaryAccount: '1234567890',
      beneficiaryIfsc: 'HDFC0001234',
      beneficiaryName: 'Alice Smith',
    },
  ],
});

console.log(`Succeeded: ${result.totalSuccessful}, Failed: ${result.totalFailed}`);
```

  </TabItem>
</Tabs>

</CodeRail>

## Error Responses

<ErrorTable
  rows={[
    { code: "VALIDATION_ERROR", status: "400", message: "Invalid request body" },
    { code: "UNAUTHORIZED", status: "401", message: "Invalid API key" },
  ]}
/>

Individual item errors are returned in the `failed` array, not as HTTP errors.

## Related Endpoints

- [Create Refund Intent](/docs/rest-api/endpoints/refunds/get-template) — Discover required fields before batch upload
- [Confirm Refund Intent](/docs/rest-api/endpoints/refunds/create-refund) — Confirm a single refund
- [List Refunds](/docs/rest-api/endpoints/refunds/list-refunds)
- [Get Refund](/docs/rest-api/endpoints/refunds/get-refund)
