Execute transfer (custody)
Executes an on-chain transfer using the exact SDP Wallet ID (id from /v1/wallets) and server-side custody signing.
POST/Run in dashboard
Requires an Idempotency-Key: a retry with the same key and request returns the original transfer instead of sending a second one. A 200 may return a processing transfer with its signature when broadcast or confirmation is still being reconciled; do not create a replacement transfer for that payment. A native SOL transfer is refused with 409 AMOUNT_BELOW_RENT_MINIMUM, before anything is written or sent, when it pays less than the rent-exempt minimum to an address with no account or would leave the source wallet holding more than nothing but less than the minimum.
Requires an API key with the transfers.execute action.
Headers
-
x-project-id·string· min length 1 · exampleprj_exampleRequired for dashboard sessions. API keys use their assigned project.
-
Idempotency-Key·string· requiredUse a unique 1–255 printable ASCII key per operation. Reuse it with the same request on retries. An in-flight attempt returns
409; a changed request returns422. Retry and recovery rules.
Request body
application/json · required
Create transfer request payload for a custody-managed source wallet. This endpoint does not provision wallets.
-
projectId·string· min length 1 · exampleprj_exampleProject identifier for the transfer context.
-
transferId·string· min length 1Existing off-ramp transfer to use for the on-chain crypto deposit. The transfer must still be awaiting payment and must not already contain on-chain submission data.
-
sourceCustodyWalletId·string· required · min length 1 · examplecwlt_exampleExact SDP Wallet ID (
idfromGET /v1/wallets). -
destination·string· required · example7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsUDestination wallet address.
-
counterpartyAccountId·string· min length 1 · examplecpacct_exampleThe counterparty's active Solana
crypto_walletaccount thatdestinationbelongs to, as a transfer batch recipient names it. The transfer record is tied to that counterparty. Its address must equaldestination, and it cannot be combined withtransferId. -
token·string· required · exampleEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vToken to transfer. Pass
SOLfor the native token, a well-known token symbol (e.g.USDC) resolved to the configured cluster's mint, or a base58 SPL mint address. Custom tokens must be specified by their on-chain mint. -
amount·string· required · example100.00Token amount in UI units (decimal string).
-
memo·string· max length 256Optional memo for the transfer.
Responses
-
200Transfer executed-
data·object· requiredTransfer response payload.
-
transfer·object· requiredTransfer details.
-
fireblocks·objectNative 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 | null· required -
status·string· required -
subStatus·string | null· required
-
-
id·string· required · min length 1 · examplexfr_exampleTransfer identifier (SDP record ID, not the on-chain signature).
-
organizationId·string· required · min length 1 · exampleorg_exampleOrganization identifier.
-
custodyWalletId·string | null· required · examplecwlt_exampleExact SDP Wallet ID associated with the persisted transfer, or null for unresolved legacy and observed rows.
-
providerWalletId·string· required · min length 1 · exampleprivy_wallet_123Provider wallet ID retained as evidence; not an execution selector.
-
projectId·string· min length 1 · exampleprj_exampleProject identifier for the transfer.
-
type·string· required · exampletransferTransfer type.
-
kind·string· requiredDashboard transaction kind derived from the transfer type and originating Payments resource.
-
direction·string· required · one ofinbound,outbound· exampleoutboundTransfer direction.
-
status·string· required · one ofpending,processing,confirmed,finalized,failed,awaiting_payment,settling,completed,canceled,expired· exampleconfirmedTransfer status.
-
signature·string | null· required · examplesig_exampleSolana transaction signature (tx id/hash).
-
serializedTx·string | null· required · min length 1 · examplebase64_tx_exampleBase64-encoded transaction payload, if available. Always null for transfer batch chunks, whose signed bytes stay server-side.
-
slot·integer | null· required · example123456Slot number, if confirmed.
-
blockTime·string | null· required · formatdate-time· example2025-01-01T00:00:00.000ZBlock time, if confirmed.
-
confirmedAt·string· formatdate-time· example2025-01-01T00:00:00.000ZWhen SDP confirmed the transfer. A ramp is confirmed once its delivery is verified onchain.
-
fee·integer | null· required · example5000Transaction fee in lamports.
-
error·string | null· required · exampleSignature failedError message if the transaction failed.
-
initiatedBy·objectInitiator that triggered the transfer.
-
type·string· required · one ofapi_key,user,system· exampleapi_keyInitiator type.
-
id·stringInitiator identifier if applicable.
-
display·stringHuman-friendly label for the initiator.
-
-
source·string· min length 32 · max length 44 · exampleSo11111111111111111111111111111111111111112Source wallet address.
-
destination·string· min length 32 · max length 44 · exampleSo11111111111111111111111111111111111111112Destination wallet address.
-
memo·string· max length 256Optional memo for the transfer.
-
rampsMemo·object· requiredFree-form key-value memo for offchain reconciliation of ramp transfers. At most 20 entries.
*·string· min length 1 · max length 256
-
token·stringToken symbol or mint address.
-
amount·string· example100.00Token amount in UI units (decimal string).
-
counterpartyId·string· examplecpty_exampleCounterparty tied to the transfer record, when available.
-
counterpartyDisplayName·string· exampleAcme StudioCurrent display name of the counterparty tied to the transfer.
-
providerReference·string· exampleramp_quote_exampleProvider quote or transaction reference used for ramp correlation.
-
deliveryMode·string· one ofhosted,manual_instructions,session_widget· examplesession_widgetRamp 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· exampleUSDFiat currency for the ramp leg.
-
fiatAmount·string· example100.00Quoted 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·objectWhat the provider reports paying out once it settles an off-ramp sale. Provider-reported: SDP cannot verify a bank payout.
-
amount·string· required · example20.82Fiat paid out, in display units. Differs from the quoted
fiatAmountwhen the provider re-prices the sale at receipt. -
currency·string· required · exampleUSDISO currency of the payout, as the provider reports it.
-
reportedAt·string· formatdate-time· example2025-01-02T00:00:00.000ZWhen the provider reported the payout.
-
-
deliveryMismatch·objectPresent while SDP holds a provider-settled ramp in
settlingbecause the chain contradicts the provider's delivery. A new provider report clears it.-
reason·string· required · one ofprovider_report_mismatch,transaction_failed,predates_transfer,not_delivered,amount_mismatch,transaction_not_found· exampleamount_mismatchWhy 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, ortransaction_not_found(no finalized transaction an hour after the report; SDP keeps checking, and a later delivery completes the transfer). -
checkedAt·string· required · formatdate-time· example2025-01-02T00:00:00.000ZWhen SDP's delivery check reached this verdict.
-
reportedAmount·string· example20What the provider reported delivering, in display units.
-
observedAmount·string· example0.5What the chain shows the quoted wallet received, in display units.
-
-
risk·objectOptional risk evaluation for the transfer.
-
provider·string· required · exampletrmRisk scoring provider.
-
score·string· required · example0.12Provider-specific risk score.
-
level·string· required · one oflow,medium,high,unknown· examplelowRisk level classification.
-
evaluatedAt·string· required · formatdate-time· example2025-01-01T00:00:00.000ZTimestamp when risk was evaluated.
-
-
createdAt·string· required · formatdate-time· example2025-01-01T00:00:00.000ZCreation timestamp.
-
updatedAt·string· required · formatdate-time· example2025-01-02T00:00:00.000ZLast update timestamp.
-
-
-
meta·object· requiredResponse metadata.
-
requestId·string· required · min length 1 · examplereq_exampleRequest identifier for tracing.
-
timestamp·string· required · formatdate-time· example2025-01-01T00:00:00.000ZISO 8601 timestamp.
-
-
-
400Bad Request-
error·object· requiredStandard error payload.
-
code·string· requiredMachine-readable error code.
-
message·string· requiredHuman-readable error message.
-
details·objectOptional error details for debugging.
*·any
-
-
meta·object-
requestId·string· min length 1 · examplereq_exampleRequest identifier for tracing.
-
-
-
401UnauthorizedSame fields as
400. -
403Forbidden. Requires thetransfers.executeaction: an API key must hold it (a module'smanageorexecutealso covers itsread), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS anderror.details.actionnames the action. Any other 403 names its own reason.Same fields as
400. -
404Not FoundSame fields as
400. -
409ConflictSame fields as
400. -
422Unprocessable EntitySame fields as
400. -
500Internal Server ErrorSame fields as
400. -
503Service UnavailableSame fields as
400.
Example request
curl -X POST 'https://api.solana.com/v1/payments/transfers' \
-H "Authorization: Bearer $SDP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"projectId": "prj_example",
"sourceCustodyWalletId": "cwlt_example",
"destination": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": "100.00"
}'Example responses
{
"data": {
"transfer": {
"fireblocks": {
"transactionId": "transaction_example",
"status": "example",
"subStatus": "example"
},
"id": "xfr_example",
"organizationId": "org_example",
"custodyWalletId": "cwlt_example",
"providerWalletId": "privy_wallet_123",
"projectId": "prj_example",
"type": "transfer",
"kind": "example",
"direction": "outbound",
"status": "confirmed",
"signature": "sig_example",
"serializedTx": "base64_tx_example",
"slot": 123456,
"blockTime": "2025-01-01T00:00:00.000Z",
"confirmedAt": "2025-01-01T00:00:00.000Z",
"fee": 5000,
"error": "Signature failed",
"initiatedBy": {
"type": "api_key"
},
"source": "So11111111111111111111111111111111111111112",
"destination": "So11111111111111111111111111111111111111112",
"rampsMemo": {
"invoice": "INV-123",
"po": "PO-9"
},
"amount": "100.00",
"counterpartyId": "cpty_example",
"counterpartyDisplayName": "Acme Studio",
"providerReference": "ramp_quote_example",
"deliveryMode": "session_widget",
"fiatCurrency": "USD",
"fiatAmount": "100.00",
"fiatPayout": {
"amount": "20.82",
"currency": "USD",
"reportedAt": "2025-01-02T00:00:00.000Z"
},
"deliveryMismatch": {
"reason": "amount_mismatch",
"checkedAt": "2025-01-02T00:00:00.000Z",
"reportedAmount": "20",
"observedAmount": "0.5"
},
"risk": {
"provider": "trm",
"score": "0.12",
"level": "low",
"evaluatedAt": "2025-01-01T00:00:00.000Z"
},
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-02T00:00:00.000Z"
}
},
"meta": {
"requestId": "req_example",
"timestamp": "2025-01-01T00:00:00.000Z"
}
}Error responses use the standard error shape.