Digital Asset On / Off-Ramp
Move customers between fiat and digital assets. The on-ramp funds a client in USD and delivers digital asset to an external wallet; the off-ramp receives digital asset and pays out USD via ACH.
Your app sends the customer's details; RAMFi runs KYC, opens the account, and returns a client record with Active status when verification passes.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| firstName | string | REQUIRED | Client's legal first name. | |
| lastName | string | REQUIRED | Client's legal last name. | |
| address | string | REQUIRED | Street address. | |
| city | string | REQUIRED | City. | |
| state | string | REQUIRED | State code. | |
| zip | string | REQUIRED | ZIP code. | |
| taxID | string | REQUIRED | SSN (or EIN for business accounts). | |
| dob | string (date) | REQUIRED | Date of birth (YYYY-MM-DD). | |
| clientID | string | REQUIRED | Your external identifier for this client. | |
| string | REQUIRED | Email address (validated). |
curl --request POST \
--url 'https://FQDN/api/FE/NewClient' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"firstName": "string",
"lastName": "string",
"taxID": "string",
"clientID": "customer-id",
"email": "user@example.com"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"firstName": "string",
"lastName": "string",
"taxID": "string",
"clientID": "customer-id",
"email": "user@example.com"
})
};
fetch('https://FQDN/api/FE/NewClient', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/NewClient"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"firstName": "string",
"lastName": "string",
"taxID": "string",
"clientID": "customer-id",
"email": "user@example.com"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json()){
"id": "string",
"clientID": "customer-id",
"accountStatus": "Active",
"email": "user@example.com"
}Active means the client is verified and can transact.Register the external wallet with RAMFi. Every address must be whitelisted and AML-screened before it can send funds to or receive funds from RAMFi — the destination wallet for on-ramp sends, and the customer’s sending wallet for off-ramp deposits. Parameters are sent as query string.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| address | string | optional | The wallet address. | |
| addressIdentifier | string | optional | A friendly name for the address. |
curl --request POST \
--url 'https://FQDN/api/FE/AddWalletAddress?address=0x...&addressIdentifier=Customer%20wallet' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>'
}
};
fetch('https://FQDN/api/FE/AddWalletAddress?address=0x...&addressIdentifier=Customer%20wallet', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/AddWalletAddress?address=0x...&addressIdentifier=Customer%20wallet"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>"
}
resp = requests.post(url, headers=headers)
print(resp.json()){
"isSuccess": true,
"result": "approved",
"errorMessage": null
}- 1YouFund in USDACH (see Funding methods)
- RAMFiCredit USD balanceRAMFi clears funds; USD tradable
- 2YouRequest conversionPOST /ConvertClientFunds
- RAMFiExecute conversionRAMFi converts at market rate
- 3YouRequest sendPOST /SendFundsToAddress
- RAMFiBroadcast transactionRAMFi signs & submits on-chain
- ChainSettles on-chainconfirmed on the blockchain
Show full interaction diagram
On-ramp sequence. App-lane boxes are API calls; RAMFi performs the work; the Blockchain lane is where the transaction confirms.
ACH, bank account
- 1
- RAMFiVerify & store bankRAMFi saves the linked account
- 2YouPull fundsPOST /AddClientFunds
- RAMFiDraft & clear (ACH)~2 business days, then credit USD
Show full interaction diagram
Link the bank once via the open-banking SDK, then pull with AddClientFunds.
Launch the Bank Connection SDK (open banking). The customer authenticates with their bank; RAMFi stores the linked account server-side. Account and routing numbers never reach your app.
const sdk = new OpenBankConnectSDK();
await sdk.init({
clientToken: 'CLIENT_ID',
onError: (e) => console.error(e)
});
sdk.open(); // customer authenticates with their bankReturned to the SDK callback, not your server: only a success signal. The linked account is held by RAMFi.
Draft the linked bank. Funds are pending ~2 business days before they are tradable.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string | REQUIRED | The client to fund. | |
| amount | number | REQUIRED | USD amount to draft via ACH. | |
| transactionDate | string (date) | optional | Date to initiate the ACH draft (YYYY-MM-DD). | |
| externalReference | string | REQUIRED | Your unique reconciliation key. |
curl --request POST \
--url 'https://FQDN/api/FE/AddClientFunds' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"amount": 250.00,
"transactionDate": "2026-01-15",
"externalReference": "ext-001"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"amount": 250.00,
"transactionDate": "2026-01-15",
"externalReference": "ext-001"
})
};
fetch('https://FQDN/api/FE/AddClientFunds', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/AddClientFunds"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"amount": 250.00,
"transactionDate": "2026-01-15",
"externalReference": "ext-001"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json())1024Wire / RTP
- 1YouGet deposit instructionsGET /GetDepositInfo
- RAMFiReturn bank details + memo IDgive these to the customer to wire against
- 2YouRecord the incoming wirePOST /AddPendingFunds
- RAMFiMatch wire, credit USDcredited once the wire lands
Show full interaction diagram
Returns the receiving bank coordinates and a memoID. Show these to the customer — the wire must carry the memoID so the funds can be attributed to them.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientId | string | optional | The client ID. Obtain via Create Client. |
curl --request GET \
--url 'https://FQDN/api/FE/GetDepositInfo?clientId=customer-id' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>'const options = {
method: 'GET',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>'
}
};
fetch('https://FQDN/api/FE/GetDepositInfo?clientId=customer-id', options)
.then(r => r.json())
.then(console.log);import requests
url = "https://FQDN/api/FE/GetDepositInfo"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>"
}
resp = requests.get(url, headers=headers, params={"clientId": "customer-id"})
print(resp.json()){
"bankAccount": "string",
"routing": "string",
"bankName": "string",
"address": "string",
"city": "string",
"state": "string",
"zip": "string",
"memoID": "string"
}Tell RAMFi a deposit is on its way so it can be matched and attributed when it lands. Set clientReference to the memoID from the previous call.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string | REQUIRED | The client the pending deposit is for. | |
| externalTransactionID | string | REQUIRED | External transaction/processor reference, known in advance. | |
| amount | number | REQUIRED | Expected deposit amount. | |
| expectedDepositDate | string (date) | optional | When the deposit is expected to clear (YYYY-MM-DD). | |
| clientReference | string | optional | Your reference for this pending deposit. |
curl --request POST \
--url 'https://FQDN/api/FE/AddPendingFunds' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"externalTransactionID": "wire-001",
"amount": 25000.00,
"expectedDepositDate": "2026-07-24",
"clientReference": "MEMO-8842"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"externalTransactionID": "wire-001",
"amount": 25000.00,
"expectedDepositDate": "2026-07-24",
"clientReference": "MEMO-8842"
})
};
fetch('https://FQDN/api/FE/AddPendingFunds', options)
.then(r => r.json())
.then(console.log);import requests
url = "https://FQDN/api/FE/AddPendingFunds"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"externalTransactionID": "wire-001",
"amount": 25000.00,
"expectedDepositDate": "2026-07-24",
"clientReference": "MEMO-8842"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json())Success - no response body is returned.memoID the customer put on the wire.Bring fiat in using the funding method above (ACH). RAMFi credits the USD balance.
Convert the USD balance to USDC at the market rate at time of conversion.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string (UUID) | REQUIRED | The client whose funds to convert. | |
| amount | number | REQUIRED | Amount to convert. | |
| fromCurrencyID | CurrencyType | REQUIRED | Source currency (see CurrencyType enum). | |
| toCurrencyID | CurrencyType | REQUIRED | Target currency (see CurrencyType enum). | |
| externalReference | string | REQUIRED | Your unique reconciliation key. |
curl --request POST \
--url 'https://FQDN/api/FE/ConvertClientFunds' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USD",
"toCurrencyID": "USDC",
"externalReference": "ext-003"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USD",
"toCurrencyID": "USDC",
"externalReference": "ext-003"
})
};
fetch('https://FQDN/api/FE/ConvertClientFunds', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/ConvertClientFunds"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USD",
"toCurrencyID": "USDC",
"externalReference": "ext-003"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json())2048USD.USDC.Send the converted digital asset on-chain to the whitelisted address. RAMFi signs and broadcasts.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| address | string | REQUIRED | Destination wallet address. Must already be whitelisted via Add Wallet Address. | |
| clientId | string | optional | The client ID. Obtain via Create Client. | |
| amount | number (double) | REQUIRED | Amount to send. Must be greater than 0.00000001 (the smallest tradeable BTC unit). | |
| currency | CurrencyType | REQUIRED | Digital-asset currency. Cannot be USD (1) or PUSD (2), and cannot be the unset default (0). See CurrencyType. | |
| externalReference | string | REQUIRED | Your transaction reference. Maximum 50 characters. |
curl --request POST \
--url 'https://FQDN/api/FE/SendFundsToAddress?clientId=customer-id&address=0x...&amount=250.00¤cy=USDC&externalReference=ext-005' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>'
}
};
fetch('https://FQDN/api/FE/SendFundsToAddress?clientId=customer-id&address=0x...&amount=250.00¤cy=USDC&externalReference=ext-005', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/SendFundsToAddress?clientId=customer-id&address=0x...&amount=250.00¤cy=USDC&externalReference=ext-005"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>"
}
resp = requests.post(url, headers=headers)
print(resp.json()){
"isSuccess": true,
"result": "0xabc...hash",
"errorMessage": null
}USDC.SendFundsToAddress fails for any address not previously registered via AddWalletAddress.- ChainCustomer sends digital assetdeposit confirmed on-chain
- 1YouLedger the depositPOST /AddFundsFromAddress
- RAMFiCredit asset balanceRAMFi records the deposit
- 2YouRequest conversionPOST /ConvertClientFunds
- RAMFiExecute conversionRAMFi converts at market rate
- 3YouRequest withdrawalPOST /WithdrawClientFunds
- RAMFiDisburse USD (ACH)RAMFi sends ACH to linked bank
Show full interaction diagram
Off-ramp sequence. App-lane boxes are API calls; RAMFi screens, records, converts, and disburses. The deposit confirms in the Blockchain lane.
After the customer sends digital asset on-chain, record it against the client using the transaction hash for verification.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string | REQUIRED | The client the deposit is for. | |
| externalTransactionID | string | REQUIRED | Your external transaction reference. | |
| amount | number | REQUIRED | Expected deposit amount. | |
| fromAddress | string | REQUIRED | External whitelisted source address. | |
| toAddress | string | REQUIRED | RAMFi custody receiving address (must be whitelisted). | |
| fundsType | CurrencyType | REQUIRED | Digital-asset currency of the deposit (CurrencyType enum, e.g. 6 = BTC). |
curl --request POST \
--url 'https://FQDN/api/FE/AddFundsFromAddress' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"externalTransactionID": "0xhash...",
"amount": 250.00,
"fromAddress": "sending-wallet",
"toAddress": "ramfi-address",
"fundsType": "USDC"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"externalTransactionID": "0xhash...",
"amount": 250.00,
"fromAddress": "sending-wallet",
"toAddress": "ramfi-address",
"fundsType": "USDC"
})
};
fetch('https://FQDN/api/FE/AddFundsFromAddress', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/AddFundsFromAddress"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"externalTransactionID": "0xhash...",
"amount": 250.00,
"fromAddress": "sending-wallet",
"toAddress": "ramfi-address",
"fundsType": "USDC"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json()){
"isSuccess": true,
"result": "ledgered",
"errorMessage": null
}USDC.Convert the credited digital-asset balance back to USD at the market rate.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string (UUID) | REQUIRED | The client whose funds to convert. | |
| amount | number | REQUIRED | Amount to convert. | |
| fromCurrencyID | CurrencyType | REQUIRED | Source currency (see CurrencyType enum). | |
| toCurrencyID | CurrencyType | REQUIRED | Target currency (see CurrencyType enum). | |
| externalReference | string | REQUIRED | Your unique reconciliation key. |
curl --request POST \
--url 'https://FQDN/api/FE/ConvertClientFunds' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USDC",
"toCurrencyID": "USD",
"externalReference": "ext-013"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USDC",
"toCurrencyID": "USD",
"externalReference": "ext-013"
})
};
fetch('https://FQDN/api/FE/ConvertClientFunds', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/ConvertClientFunds"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"amount": 250.00,
"fromCurrencyID": "USDC",
"toCurrencyID": "USD",
"externalReference": "ext-013"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json())2049USDC.USD.Disburse the USD to the client's linked bank account by ACH, or push to a debit card with PushToCard. Both are fiat-out legs; pick the rail your product offers.
| Field | Type | Required | Description | Value |
|---|---|---|---|---|
| clientID | string | REQUIRED | The client to withdraw from. | |
| amount | number | REQUIRED | Amount to withdraw. Digital-asset withdrawals are backed by a custody transfer. | |
| externalReference | string | REQUIRED | Your unique reconciliation key. | |
| notes | string | optional | Free-form notes recorded with the withdrawal. |
curl --request POST \
--url 'https://FQDN/api/FE/WithdrawClientFunds' \
--header 'Authorization: Bearer <SHORT_TERM_TOKEN>' \
--header 'X-OTP: <X_OTP>' \
--header 'Content-Type: application/json' \
--data '{
"clientID": "customer-id",
"amount": 250.00,
"externalReference": "ext-014"
}'const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer <SHORT_TERM_TOKEN>',
'X-OTP': '<X_OTP>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"clientID": "customer-id",
"amount": 250.00,
"externalReference": "ext-014"
})
};
fetch('https://FQDN/api/FE/WithdrawClientFunds', options)
.then(r => r.json())
.then(console.log)
.catch(console.error);import requests
url = "https://FQDN/api/FE/WithdrawClientFunds"
headers = {
"Authorization": "Bearer <SHORT_TERM_TOKEN>",
"X-OTP": "<X_OTP>",
"Content-Type": "application/json"
}
payload = {
"clientID": "customer-id",
"amount": 250.00,
"externalReference": "ext-014"
}
resp = requests.post(url, headers=headers, json=payload)
print(resp.json())3072externalReference / externalTransactionID on every movement and pull GET /Reporting/Transactions or ClientLedger/{{clientId}} to reconcile.