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.
Initiate an offramp payment for an existing customer
X-Api-Key<token>API key in format: sti_env_prefix_secret
idempotency-key*stringRequired 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.
^([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)$uuidRequest schema for creating a transfer
application/json- body
Request schema for creating a transfer
quote_id*QuoteIdUUID
^([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)$uuidcustomer_id*CustomerIdCustomer UUID
^([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)$uuiddestination*|metadata?purpose_code?stringPurpose of the transfer
"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?stringFund 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.
"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).
Default Response
application/json- response
Response schema for a transfer
id*string^([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)$uuidtenant_id*string^([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)$uuidcustomer_id*string^([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)$uuidquote_id*string^([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)$uuidtype*stringType of transfer: offramp (crypto to fiat) or onramp (fiat to crypto)
"offramp""onramp"status*stringStatus of the transfer in display format
"created""unknown""compliance_hold""awaiting_funds_collection""funds_collected""in_progress""payment_submitted""payment_processed""completed""failed""cancelled""expired"created_at*string^(?:(?:\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))$date-timeupdated_at*string^(?:(?:\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))$date-timemetadata?source?|nullsource_deposit_instructions?||nulldestination?||nullfees?|nullexchange_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"api""api""otc"compliance_hold?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" }}'{ "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
| Value | Behaviour |
|---|---|
fund_transfer | Default. Stables issues single-use source_deposit_instructions and waits for the deposit. |
prefund_balance | Debits 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_enabledistrueon 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.
| Code | Meaning |
|---|---|
PREFUND_INSUFFICIENT_BALANCE | Your balance of the source asset on the source network is below the source amount. |
PREFUND_UNSUPPORTED_ASSET | The quote's source is not a supported balance asset. |
PREFUND_WALLET_NOT_ACTIVE | You have no active deposit address on the source network. |
PREFUND_TRANSFERS_NOT_ENABLED | Paying transfers from your balance is not enabled for your organisation. |
PREFUND_NOT_ENABLED | Balances are not enabled for your organisation. |
PREFUND_UNSUPPORTED_ROUTE | This 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.