# Forced transfer

Source: https://docs.settlemint.com/docs/developer-guides/asset-servicing/forced-transfer
Execute governed exception transfers through the custodian-only forced-transfer API.



A forced transfer moves assets from one holder address to another without the
holder initiating or signing the movement. Use it only for governed exception
cases, such as legal orders, approved recovery procedures, or compliance events
that require a custodian to move assets. The API submits one contract call for a
single item, a batch contract call for small lists, and durable queued execution
for larger reviewed lists.

<Mermaid
  chart="`
flowchart TD
Approval[&#x22;Approved exception record&#x22;] --> Custodian[&#x22;Custodian integration&#x22;]
Custodian --> API[&#x22;POST /api/v2/tokens/{tokenAddress}/forced-transfers&#x22;]
API --> Role[&#x22;Custodian role and wallet verification&#x22;]
Role --> Count{&#x22;Items in transfers array&#x22;}
Count -->|&#x22;1&#x22;| Single[&#x22;forcedTransfer contract call&#x22;]
Count -->|&#x22;2 to 10&#x22;| Batch[&#x22;batchForcedTransfer contract call&#x22;]
Count -->|&#x22;More than 10&#x22;| Queue[&#x22;Queued execution in chunks&#x22;]
Single --> Result[&#x22;Transaction result&#x22;]
Batch --> Result
Queue --> Result
Result --> Record[&#x22;Approval record, addresses, amounts, and transaction hashes&#x22;]
Result --> Events[&#x22;Holder balances and token events&#x22;]
`"
/>

The diagram shows the execution boundary. DALP validates the custodian caller and wallet verification before it chooses
the contract path. One item uses `forcedTransfer`. A small reviewed list uses `batchForcedTransfer`. A larger list is
accepted as queued work and can produce more than one transaction hash as chunks finish.

A forced transfer is an exception path that moves token supply through custodian authority rather than holder initiation.

<Mermaid
  chart="`
flowchart TD
Custodian[&#x22;Custodian integration&#x22;] --> API[&#x22;DALP forced-transfer API&#x22;]
API --> Role[&#x22;Custodian role check&#x22;]
API --> Source[&#x22;Source holder wallet&#x22;]
API --> Target[&#x22;Target holder wallet&#x22;]
Source --> Compliance[&#x22;Transfer compliance checks&#x22;]
Target --> Compliance
Compliance --> Chain[&#x22;On-chain forced transfer&#x22;]
Chain --> Balances[&#x22;Updated holder balances&#x22;]
`"
/>

For the web interface workflow, see [Forced transfer](/docs/user-guides/asset-servicing/forced-transfer).
For the broader holder-transfer API reference, see [Token holders and transfers](/docs/developer-guides/api-integration/token-holders-transfers).

<Callout type="warning" title="Custodian-only exception operation">
  Forced transfers bypass holder authorization. The caller must have the asset's Custodian role, and the institution
  should keep the business reason, approval evidence, affected addresses, amount, and transaction result in its own
  operating record.
</Callout>

## Prerequisites [#prerequisites]

* API key for a user with the asset's **Custodian** role
* Wallet verification method for the signing account, when required by the platform
* Token contract address for the asset you want to administer
* Source holder address, recipient address, and amount in the asset's base units
* Approval evidence for the exception workflow

## Execute one forced transfer [#execute-one-forced-transfer]

Send the token address in the path and the forced-transfer item in the `transfers`
array:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/forced-transfers \
  -H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "transfers": [
      {
        "from": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
        "recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
        "amount": "1000000000000000000"
      }
    ]
  }'
```

The API validates the caller's role, wallet verification, request shape, and token state before it submits or queues the
transaction. A synchronous response returns the token resource with `meta.txHashes`. An asynchronous response returns a
`transactionId`, `status`, and `statusUrl` for polling.

```json
{
  "transactionId": "018f2f2b-7c8a-7b40-9d46-5b5a51b8e3a1",
  "status": "accepted",
  "statusUrl": "/api/v2/transaction-requests/018f2f2b-7c8a-7b40-9d46-5b5a51b8e3a1"
}
```

Store the transaction identifier or hash with the approval evidence for the exception.

## Understand batch execution [#understand-batch-execution]

The `transfers` array decides how DALP submits the operation:

| Request shape      | Execution path                                                                        | What to record                                                                               |
| ------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| 1 item             | One `forcedTransfer` contract call                                                    | Source holder, recipient, amount, approval reason, and transaction result                    |
| 2 to 10 items      | One `batchForcedTransfer` contract call                                               | The reviewed list and the transaction result for the batch                                   |
| More than 10 items | One queued durable batch workflow that splits execution into multiple on-chain chunks | The source file or approval list, the queued operation result, and any failed item follow-up |

The request schema accepts at least one item and at most 10,000 items. Use an idempotency key when retrying the same API submission so a network retry does not create a duplicate queued operation. Large batches can surface more than one transaction hash under that queued operation.

Use this decision rule before submitting a reviewed list:

1. Send one item when the exception affects one holder balance.
2. Send 2 to 10 items when the reviewed exception list is small enough to treat as one operational batch.
3. Send more than 10 items only when the approval record already covers the full list and your reconciliation process can track multiple transaction hashes under one queued operation.

## Execute batch forced transfers [#execute-batch-forced-transfers]

The same endpoint accepts multiple forced-transfer items. Each item carries its
own source address, recipient address, and amount:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/forced-transfers \
  -H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "transfers": [
      {
        "from": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
        "recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
        "amount": "1000000000000000000"
      },
      {
        "from": "0x1234567890AbCdEf1234567890AbCdEf12345678",
        "recipient": "0xabcdef1234567890abcdef1234567890abcdef12",
        "amount": "500000000000000000"
      }
    ]
  }'
```

Batch forced-transfer requests can include up to 10,000 items. For operational control, keep each API request aligned
with one reviewed approval record. If your approval process creates separate case files, submit separate requests so
each result maps cleanly to the case that authorized it.

## Request body [#request-body]

| Field                   | Type   | Required           | Description                                                                                                             |
| ----------------------- | ------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `transfers`             | array  | Yes                | Forced-transfer items to execute. The array must contain at least one item and can contain up to 10,000 items.          |
| `transfers[].from`      | string | Yes                | Holder address losing the assets.                                                                                       |
| `transfers[].recipient` | string | Yes                | Address receiving the assets.                                                                                           |
| `transfers[].amount`    | string | Yes                | Amount to move in the asset's base units. See [Asset decimals](/docs/developer-guides/api-integration/asset-decimals).  |
| `walletVerification`    | object | Platform-dependent | Wallet verification payload when your platform requires PIN, OTP, or secret-code authorization for the signing account. |

## What to verify after submission [#what-to-verify-after-submission]

After the request queues or completes, verify the operation with the same evidence standards you use for other
asset-servicing exceptions:

1. Confirm the affected holder balances or transfer events through your operating view or API integration.
2. Record the token address, source address, recipient address, amount, approving operator, request identifier, and transaction hashes.
3. Keep the legal or compliance approval evidence outside the API request.
4. For queued batches, reconcile every completed chunk before closing the operating record.

## Troubleshooting [#troubleshooting]

| Issue                          | Check                                                                                                                 |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized`             | Confirm the API key is present, active, and scoped to the platform.                                                   |
| `403 USER_NOT_AUTHORIZED`      | Confirm the API user has the Custodian role for the asset.                                                            |
| Token is paused                | Unpause the token before attempting holder movement.                                                                  |
| Source balance is insufficient | Check the source holder's available balance and any freeze state before retrying.                                     |
| Recipient is rejected          | Verify the recipient is the intended address for the exception workflow and meets the asset's recipient requirements. |
| Batch request is rejected      | Confirm every item has `from`, `recipient`, and `amount`, and that the batch does not exceed 10,000 items.            |

## Related guides [#related-guides]

* [Forced transfer](/docs/user-guides/asset-servicing/forced-transfer) for the web interface workflow
* [Token holders and transfers](/docs/developer-guides/api-integration/token-holders-transfers) for transfer, freeze, unfreeze, and recovery endpoints
* [Token events](/docs/developer-guides/api-integration/token-events) for event-based reconciliation after the write
* [Track asynchronous transactions](/docs/developer-guides/operations/transaction-tracking) for polling queued transaction status
* [Per-asset RBAC](/docs/architecture/components/asset-contracts/rbac) for Custodian role context
