---
title: "Retry a transfer batch's failed recipients"
method: POST
path: "/v1/payments/transfer-batches/{batchId}/retry"
tags: ["Payments"]
---

# Retry a transfer batch's failed recipients

`POST /v1/payments/transfer-batches/{batchId}/retry`

## Request

- Base URL: `http://localhost:8787`
- URL: `http://localhost:8787/v1/payments/transfer-batches/{batchId}/retry`
- Auth: HTTP bearer

Sends the batch's `failed` recipients again, from the same source wallet with the batch's options, and no other recipient. A failed recipient was not paid: its transaction never reached the network or the network rejected it. Recipients still `pending` or `processing` may land and are never sent again. Each resent recipient keeps its row and links to its new chunk transfer. Takes no body and requires an Idempotency-Key: a retry with the same key sends nothing again: while its response is stored, it returns that saved response, and after a server error or once the response has expired, the batch as it stands. Read the batch's current state with `GET /v1/payments/transfer-batches/{batchId}`, and use a new key for each retry you mean to send. Any credential that may send from the batch's wallet can retry it. Each recipient is paid the address the batch recorded for it. Returns 409, sending nothing: `TRANSFER_BATCH_NOTHING_TO_RETRY` when the batch has no failed recipients (while it is `processing`, its unsent recipients become `failed` only once its request can no longer send them), `TRANSFER_BATCH_RETRY_CLAIMED` when a concurrent retry claimed them first, and `TRANSFER_BATCH_DESTINATION_CHANGED` (`details.recipientIds`) when a failed recipient's counterparty account has changed its address since (send that recipient in a new batch). A native SOL retry is refused with 409 `AMOUNT_BELOW_RENT_MINIMUM`, as create is, when a failed recipient would still be paid less than the rent-exempt minimum at an address with no account; its recipients stay `failed`. Only recipients that are still `failed` are ever sent, so a retry under a new key never pays anyone twice.

## Path parameters

- `batchId` string, required: Transfer batch identifier.

## Headers

- `x-project-id` string: Required for dashboard sessions. API keys use their assigned project.
- `Idempotency-Key` string, required: Required: a request without one returns 400 `IDEMPOTENCY_KEY_REQUIRED`. 1-255 printable ASCII characters; send a UUID per logical operation and reuse it on every retry. Within 24 hours, a retry with the same key and the same request returns the stored status and body with the response header `Idempotent-Replayed: true`. Keys are scoped to the credential: within 24 hours, the same key with a different request, or from a different credential, returns 422 `IDEMPOTENCY_KEY_REUSED`; after that, the same key from a different credential runs as a new request. A rotated API key is a new credential, so after any rotation read the resource with GET to see whether the original request ran, and never resend a money request under the new credential without checking first. A key whose original request is still running returns 409 `IDEMPOTENCY_KEY_IN_FLIGHT` with `Retry-After`. A 400, 401, 403, 409 or 429 refusal is not stored, so the same key runs again once the cause is fixed. Access is checked before the key on every request, so a refused request never claims the key, and a credential that has since lost access to the named resource gets 403 instead of the stored response. A replay is a snapshot of the original response: read the current state with GET instead of sending the request again. If the original request never recorded its outcome (it crashed), a same-key retry gets 409 `IDEMPOTENCY_KEY_IN_FLIGHT` until 15 minutes plus the route's lease have passed since the original was sent (about 16 minutes, or about 20 for transfer batches), then takes over and is answered from the record it wrote. Once the request has written its record, any answer below 500 is stored: a transfer that fails after that (such as 400 `TRANSACTION_FAILED` or a 409 signer mismatch) answers a same-key retry with the same error and `Idempotent-Replayed: true`, and after 24 hours the record answers with the same status and code. A 5xx is not stored: a same-key retry runs again and is answered from the record. To try again after a failure, send the request with a new key. After a 5xx, a retry with the same key runs again and answers from the original record instead of acting twice; that answer also carries `Idempotent-Replayed: true`.

## Response `200`

Failed recipients resent

- object: Standard success response envelope.
  - `data` object, required: Transfer batch response payload.
    - `batch` object, required: Transfer batch details.
      - `id` string, required: Transfer batch identifier.
      - `organizationId` string, required: Organization identifier.
      - `projectId` string, required: Project identifier for the transfer batch.
      - `externalId` string, nullable, required: Caller-provided batch correlation ID.
      - `sourceCustodyWalletId` string, nullable, required: Exact source SDP Wallet ID, or null for unresolved legacy batches.
      - `sourceProviderWalletId` string, required: Source Provider wallet ID retained as evidence.
      - `sourceAddress` string, required: Source custody wallet address.
      - `token` string, required: Token symbol or mint address.
      - `status` 'pending' | 'processing' | 'confirmed' | 'failed' | 'partially_failed' | 'archived', required: Transfer batch status.
      - `totalAmount` string, nullable, required: Total requested batch amount in UI units, when computed.
      - `recipientCount` integer, required: Number of recipient rows in the batch.
      - `transactionCount` integer, required: Number of Solana transaction chunks for the batch.
      - `createdAt` string, date-time, required: ISO 8601 timestamp.
      - `updatedAt` string, date-time, required: ISO 8601 timestamp.
    - `recipients` object[]: Recipient rows for the batch, included on detail responses.
      - `id` string, required: Transfer batch recipient identifier.
      - `batchId` string, required: Parent transfer batch identifier.
      - `transferId` string, nullable, required: Payment transfer chunk that settled this recipient, once assigned.
      - `externalId` string, nullable, required: Caller-provided recipient correlation ID.
      - `counterpartyId` string, required: Counterparty identifier for the recipient.
      - `counterpartyAccountId` string, required: Counterparty account identifier for the recipient.
      - `destination` string, required: Resolved Solana destination address.
      - `amount` string, required: Token amount in UI units (decimal string).
      - `status` 'pending' | 'processing' | 'confirmed' | 'failed' | 'archived', required: Transfer batch recipient status.
      - `error` string, nullable, required: Recipient-level failure reason, when available.
      - `createdAt` string, date-time, required: ISO 8601 timestamp.
      - `updatedAt` string, date-time, required: ISO 8601 timestamp.
    - `transfers` object[]: Payment transfer chunk records associated with this batch.
      - `fireblocks` object: Native Fireblocks transfer tracking. A processing payment may await approval in Fireblocks; network fees are paid by its source vault. A missing transaction ID means submission is still being reconciled. Do not create a replacement payment while its outcome is unknown.
        - `transactionId` string, nullable, required
        - `status` string, required
        - `subStatus` string, nullable, required
      - `id` string, required: Transfer identifier (SDP record ID, not the on-chain signature).
      - `organizationId` string, required: Organization identifier.
      - `custodyWalletId` string, nullable, required: Exact SDP Wallet ID associated with the persisted transfer, or null for unresolved legacy and observed rows.
      - `providerWalletId` string, required: Provider wallet ID retained as evidence; not an execution selector.
      - `projectId` string: Project identifier for the transfer.
      - `type` string, required: Transfer type.
      - `kind` string, required: Dashboard transaction kind derived from the transfer type and originating Payments resource.
      - `direction` 'inbound' | 'outbound', required: Transfer direction.
      - `status` 'pending' | 'processing' | 'confirmed' | 'finalized' | 'failed' | 'awaiting_payment' | 'settling' | 'completed' | 'canceled' | 'expired', required: Transfer status.
      - `signature` string, nullable, required: Solana transaction signature (tx id/hash).
      - `serializedTx` string, nullable, required: Base64-encoded transaction payload, if available. Always null for transfer batch chunks, whose signed bytes stay server-side.
      - `slot` integer, nullable, required: Slot number, if confirmed.
      - `blockTime` string, date-time, nullable, required: Block time, if confirmed.
      - `confirmedAt` string, date-time: When SDP confirmed the transfer. A ramp is confirmed once its delivery is verified onchain.
      - `fee` integer, nullable, required: Transaction fee in lamports.
      - `error` string, nullable, required: Error message if the transaction failed.
      - `initiatedBy` object: Initiator that triggered the transfer.
        - `type` 'api_key' | 'user' | 'system', required: Initiator type.
        - `id` string: Initiator identifier if applicable.
        - `display` string: Human-friendly label for the initiator.
      - `source` string: Source wallet address.
      - `destination` string: Destination wallet address.
      - `memo` string: Optional memo for the transfer.
      - `rampsMemo` object, required: Free-form key-value memo for offchain reconciliation of ramp transfers. At most 20 entries.
      - `token` string: Token symbol or mint address.
      - `amount` string: Token amount in UI units (decimal string).
      - `counterpartyId` string: Counterparty tied to the transfer record, when available.
      - `counterpartyDisplayName` string: Current display name of the counterparty tied to the transfer.
      - `providerReference` string: Provider quote or transaction reference used for ramp correlation.
      - `deliveryMode` 'hosted' | 'manual_instructions' | 'session_widget': Ramp delivery mode. Hosted flows require the customer to complete a provider-hosted UI; manual instructions require the customer to fund displayed instructions; session widgets use a provider SDK session.
      - `fiatCurrency` string: Fiat currency for the ramp leg.
      - `fiatAmount` string: Quoted fiat amount for the ramp leg: what the customer pays on an on-ramp, the payout quoted on an off-ramp. It never changes after the quote; an off-ramp's final payout is `fiatPayout`.
      - `fiatPayout` object: What the provider reports paying out once it settles an off-ramp sale. Provider-reported: SDP cannot verify a bank payout.
        - `amount` string, required: Fiat paid out, in display units. Differs from the quoted `fiatAmount` when the provider re-prices the sale at receipt.
        - `currency` string, required: ISO currency of the payout, as the provider reports it.
        - `reportedAt` string, date-time: When the provider reported the payout.
      - `deliveryMismatch` object: Present while SDP holds a provider-settled ramp in `settling` because the chain contradicts the provider's delivery. A new provider report clears it.
        - `reason` 'provider_report_mismatch' | 'transaction_failed' | 'predates_transfer' | 'not_delivered' | 'amount_mismatch' | 'transaction_not_found', required: Why the chain contradicts the provider: `provider_report_mismatch` (its report contradicts SDP's record), `transaction_failed`, `predates_transfer` (the transaction landed before the transfer), `not_delivered` (nothing of the quoted asset reached the quoted wallet), `amount_mismatch`, or `transaction_not_found` (no finalized transaction an hour after the report; SDP keeps checking, and a later delivery completes the transfer).
        - `checkedAt` string, date-time, required: When SDP's delivery check reached this verdict.
        - `reportedAmount` string: What the provider reported delivering, in display units.
        - `observedAmount` string: What the chain shows the quoted wallet received, in display units.
      - `risk` object: Optional risk evaluation for the transfer.
        - `provider` string, required: Risk scoring provider.
        - `score` string, required: Provider-specific risk score.
        - `level` 'low' | 'medium' | 'high' | 'unknown', required: Risk level classification.
        - `evaluatedAt` string, date-time, required: Timestamp when risk was evaluated.
      - `createdAt` string, date-time, required: Creation timestamp.
      - `updatedAt` string, date-time, required: Last update timestamp.
  - `meta` object, required: Response metadata.
    - `requestId` string, required: Request identifier for tracing.
    - `timestamp` string, date-time, required: ISO 8601 timestamp.

## Other responses

- `400`: Error
- `401`: Error
- `403`: Forbidden. Requires the `transfer_batches.execute` action: an API key must hold it (a module's `manage` or `execute` also covers its `read`), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS and `error.details.action` names the action. Any other 403 names its own reason.
- `404`: Error
- `409`: Error
- `422`: Error
- `500`: Error

## Changes

- **2026-11-02** `9f0b1d561064`: 7 warning
  - added the new `CUSTODY_POLICY_REFUSED` enum value to the `error/code` response property for the response status `400`
  - added the new `CUSTODY_POLICY_REFUSED` enum value to the `error/code` response property for the response status `401`
  - added the new `CUSTODY_POLICY_REFUSED` enum value to the `error/code` response property for the response status `403`
  - added the new `CUSTODY_POLICY_REFUSED` enum value to the `error/code` response property for the response status `404`
  - …3 more
- **2026-10-30** `f4a0135eca08`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/solana/apis/solana-developer-platform-api/changes/v1/payments/transfer-batches/:batchId/retry/post.md)

---

[API](https://skmtc.dev/solana/apis/solana-developer-platform-api.md) - [All operations](https://skmtc.dev/solana/apis/solana-developer-platform-api/llms.txt) - [OpenAPI document](https://skmtc.dev/solana/apis/solana-developer-platform-api/revisions/47682f5c382e?raw)
