Create Transfer from Balance

Pay for a transfer from the balance your organisation already holds instead of collecting a deposit from your customer. This is the standard Create Transfer endpoint with funding_source set.

POST
/api/v1/transfer

Initiate an offramp payment for an existing customer

Authorization

headerX-Api-Key<token>

API key in format: sti_env_prefix_secret

Header Parameters

idempotency-key*string

Required for mutating calls. Use a V4 UUID. The server stores the first result for 24h and returns it on subsequent retries with the same key.

Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid

Request Body

Request schema for creating a transfer

application/json
  1. body

Request schema for creating a transfer

quote_id*QuoteId

UUID

Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
customer_id*CustomerId

Customer UUID

Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
destination*|
metadata?
purpose_code?string

Purpose of the transfer

Value in"PAYMENT_FOR_SERVICES""PAYMENT_FOR_SOFTWARE""PAYMENT_FOR_IMPORTED_GOODS""TRAVEL_SERVICES""VENDOR_CONTRACTOR_PAYOUTS""TRANSFER_TO_OWN_ACCOUNT""INVESTMENT_IN_SHARES""PURCHASE_OF_PROPERTY""REPAYMENT_OF_LOANS""SALARY""PAYMENT_OF_PROPERTY_RENTAL""INFORMATION_SERVICE_CHARGES""ADVERTISING_PR_EXPENSES""ROYALTY_FEES""BROKER_COMMITMENT_FEES""ADVISOR_TECHNICAL_FEES""REPRESENTATIVE_OFFICE_EXPENSES""TAX_PAYMENT""TRANSPORTATION_FEES_FOR_GOODS""CONSTRUCTION_COSTS""INSURANCE_PREMIUM""GENERAL_GOODS_TRADES""INSURANCE_CLAIMS_PAYMENT""FAMILY_MAINTENANCE""EDUCATION_EXPENSES""MEDICAL_TREATMENT""DONATIONS""MUTUAL_FUND_INVESTMENT""CURRENCY_EXCHANGE""USER_WITHDRAWAL""ADVANCE_PAYMENTS_FOR_GOODS""VENDOR_CONTRACTOR_SOFTWARE_PAYOUTS""MERCHANT_SETTLEMENT""REPATRIATION_FUND_SETTLEMENT""PERSONAL_REMITTANCE""RETURN_OF_FUNDS"
funding_source?string

Fund the transfer from the tenant's prefund balance ("prefund_balance") instead of a customer crypto deposit ("fund_transfer", the default). "prefund_balance" requires a quote whose source is a supported balance asset (USDC or USDT on ethereum, polygon or solana, or USDT on tron) and enough balance of that asset on that network; the workflow starts immediately without deposit instructions. The legacy value "customer_deposit" is still accepted but deprecated.

Value in"prefund_balance""fund_transfer""customer_deposit"
source_wallet?

Custody of the wallet the customer will send from. Required by the travel rule to decide whether the sender is a VASP ("hosted") or the customer themselves ("self_hosted", the default when omitted).

Response Body

Default Response

application/json
  1. response

Response schema for a transfer

id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
tenant_id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
customer_id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
quote_id*string
Match^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
Formatuuid
type*string

Type of transfer: offramp (crypto to fiat) or onramp (fiat to crypto)

Value in"offramp""onramp"
status*string

Status of the transfer in display format

Value in"created""unknown""compliance_hold""awaiting_funds_collection""funds_collected""in_progress""payment_submitted""payment_processed""completed""failed""cancelled""expired"
created_at*string
Match^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
Formatdate-time
updated_at*string
Match^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
Formatdate-time
metadata?
source?|null
source_deposit_instructions?||null
destination?||null
fees?|null
exchange_rate?|

Exchange rate (1 source = X destination). On Price Lock transfers it is the locked commercial rate: the market benchmark after the conversion fee only, processing fee excluded (see fees.processing_fee)

origin*string
Default"api"
Value in"api""otc"
compliance_hold?
Example Request
curl -X POST 'https://api.sandbox.stables.money/api/v1/transfer' \  -H 'idempotency-key: 497f6eca-6276-4993-bfeb-53cbbbba6f08' \  -H 'Content-Type: application/json' \  -d '{  "quote_id": "3c071a1d-db86-46a7-9dc8-72ba3fbca992",  "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",  "destination": {    "type": "bank",    "account_holder_name": "string",    "bank_name": "string",    "bank_country": "st",    "currency": "str"  }}'
Example Response
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "tenant_id": "34f5c98e-f430-457b-a812-92637d0c6fd0",  "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",  "quote_id": "3c071a1d-db86-46a7-9dc8-72ba3fbca992",  "type": "offramp",  "status": "created",  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z",  "metadata": {    "property1": "string",    "property2": "string"  },  "source": {    "amount": "string",    "currency": "string",    "network": "string",    "funding_source": "prefund_balance",    "wallet": {      "type": "hosted",      "vasp_name": "string"    }  },  "source_deposit_instructions": {    "wallet_address": "string",    "currency": "string",    "network": "string",    "amount": "string"  },  "destination": {    "amount": "string",    "currency": "string",    "network": "string",    "wallet_address": "string"  },  "fees": {    "fx_fee": {      "amount": "string",      "currency": "string"    },    "integrator_fee": {      "amount": "string",      "currency": "string"    },    "developer_fee": {      "amount": "string",      "currency": "string"    },    "platform_fee": {      "amount": "string",      "currency": "string"    },    "processing_fee": {      "amount": "string",      "currency": "string"    },    "withdrawal_fee": {      "amount": "string",      "currency": "string"    },    "payment_method_fee": {      "amount": "string",      "currency": "string"    },    "network_fee": {      "amount": "string",      "currency": "string"    },    "total_fee": {      "amount": "string",      "currency": "string"    }  },  "exchange_rate": 0,  "origin": "api",  "compliance_hold": {    "rfi_id": "67dff3e5-8fe4-4fc3-bd34-9ccc65996247",    "reasons": [      {        "reason_code": "string",        "description": "string"      }    ],    "requirements": [      {        "type": "KYC_CAPABILITY",        "required_capability": "string",        "required_level": "string",        "suggested_level": "string",        "required_evidence": "string",        "requirement_id": "6bcf6ec7-61c3-4af0-a310-59616a4dac61",        "status": "missing"      }    ]  }}

Funding source

ValueBehaviour
fund_transferDefault. Stables issues single-use source_deposit_instructions and waits for the deposit.
prefund_balanceDebits your balance of the quote's source asset on its network. No deposit instructions; starts immediately.

The legacy value customer_deposit is still accepted as a deprecated alias of fund_transfer.

The response reports it as source.funding_source (prefund_balance or fund_transfer), and source_deposit_instructions is null for balance-funded transfers.

Requirements

  • transfers_enabled is true on Get Balance.
  • The quote's source is USDC or USDT on Ethereum, Polygon or Solana, or USDT on Tron.
  • You hold an active deposit address on that network, with enough of that asset to cover the source amount. Assets and networks are not pooled.

Errors

Balance funding is checked before the transfer is created. Each failure returns 422, with the code as a prefix of message. There is no separate code field.

CodeMeaning
PREFUND_INSUFFICIENT_BALANCEYour balance of the source asset on the source network is below the source amount.
PREFUND_UNSUPPORTED_ASSETThe quote's source is not a supported balance asset.
PREFUND_WALLET_NOT_ACTIVEYou have no active deposit address on the source network.
PREFUND_TRANSFERS_NOT_ENABLEDPaying transfers from your balance is not enabled for your organisation.
PREFUND_NOT_ENABLEDBalances are not enabled for your organisation.
PREFUND_UNSUPPORTED_ROUTEThis corridor cannot be paid from a balance.

The pre-creation balance check is advisory. The authoritative debit happens atomically when the transfer runs, so a transfer created against a balance spent concurrently can still fail after creation. Creating one spends real funds immediately, with no deposit step to abandon. When retrying, reuse the same Idempotency-Key (required on this endpoint) so a retry cannot pay twice. See Idempotency.

Related: Transfer from balance walks through the full flow with examples.