FX & Currency
MyStocks-managed conversion for supported non-USD funding and withdrawals, plus execution and dividend FX. All wallets remain denominated in USD.
MyStocks keeps every partner and sub-account wallet in USD. For supported currencies, you can send a local-currency funding or withdrawal amount and MyStocks converts it to the USD ledger amount using the managed FX table. Your integration still owns the payment rail that collects or disburses the local currency.
| Flow | Who handles FX? | What you provide | What MyStocks does |
|---|---|---|---|
| Funding (deposit) | MyStocks | Local amount + ISO currency | Converts with managed FX and credits the USD wallet |
| Equity trading | MyStocks | — | Applies the execution rate and settles in the local market currency |
| Dividends | MyStocks | — | Converts the declared dividend to USD and credits the sub-account wallet |
| Withdrawal | MyStocks | Local amount + ISO currency | Converts with managed FX and debits the USD wallet; partner completes payout |
Funding and withdrawal conversion
Send the local amount in amount and its ISO 4217 code in currency. The response records the USD
ledger amount, rate, source, and timestamp. Bulk GET /fx/rates export is disabled in live and sandbox with 403 FX_RATE_EXPORT_DISABLED under the current daily provider license. Use an FX quote to obtain conversion terms.
The conversion metadata uses fxSource: "MYSTOCKS_MANAGED_FX". You may instead send amountUsd
when the integration has already determined the exact USD ledger amount. The fxRate request field
is deprecated and retained only for legacy USD-ledger integrations; when supplied, it is identified
as PARTNER_PROVIDED_LEGACY. Do not use it for new integrations.
/fx/rates
Bulk FX rate export disabled
Full endpoint documentationlistFxRatesBulk rate export is disabled under the current daily provider license in live and sandbox. Use POST /fx/quote or POST /users/{userId}/fx/quote for permitted conversions.
Parameters
This endpoint has no path or query parameters.
Headers
AuthorizationstringRequiredBearer sk_sandbox_<your_key> for sandbox requests.
Response fields
See the response format and API Reference for this endpoint.
Response codes
401Unauthorized.403FX_RATE_EXPORT_DISABLED. Bulk redistribution is not licensed.Trading FX
When a BUY order executes on the NSE, MyStocks converts the USD amount to KES at the execution rate.
The fillPrice in the order response is expressed in the stock's native currency (for example, KES
for NSE). MyStocks debits the sub-account USD wallet using the applied rate.
FX risk. Sub-account wallets are denominated in USD. Exchange-rate movements between funding and trade execution are borne by the end-user. MyStocks does not offer hedging; ensure your product disclosures reflect this.
Are the returned currency views separate wallets?
No. The current walletContractVersion: 1 uses USD_LEDGER_WITH_INDICATIVE_EQUIVALENTS. wallet is the actual USD balance. Entries in wallets, buyingPower and withdrawable are converted display equivalents, labelled balanceKind: INDICATIVE_EQUIVALENT, indicative: true, spendable: false. Do not add these values or treat KES and NGN views as separate funds. USD 100 shown in three currencies remains one balance.
Sandbox returns the same indicative fields using managed rates where available. If display rates are unavailable, the actual USD balance remains readable and only a USD view is returned; no local rate is invented. Sandbox money and its settlement behavior remain virtual. Native multicurrency wallet and FX workflows require a separate explicit contract; the v1 local-currency deposit still converts into USD.
How do I read actual currency wallets?
Opt in with GET /users/{userId}/wallet?walletContract=2 or X-MyStocks-Wallet-Contract: 2; the same option is available on /account. The additive currencyWallets list reads actual existing wallet documents. The old wallet and indicative wallets fields retain their meaning. walletContractVersion: 2 uses NATIVE_CURRENCY_WALLETS.
Each actual wallet reports cashBalance, reserved, externallyReserved, totalCash, available, withdrawable, eligibility and conversion eligibility. Trading escrow already debited from cash is shown separately: USD cash 100 and external escrow 25 means total cash 125, available cash 100. Do not subtract the external escrow again. Live non-USD native funding, payouts and conversion remain disabled pending operational enablement; Sandbox explicit FX is available once virtual funds have traceable origin. A positive balance alone does not authorize an operation. Existing funded but ineligible wallets remain visible.
Residence-based zero-wallet provisioning already uses the main platform policy in both environments. It never copies USD display equivalents into local wallet balances. No production balance migration is performed by reading this endpoint. Wallet transaction history remains the account transactions endpoint, with the actual entry currency preserved.
How do I convert actual wallet funds?
Create an owned-customer quote with POST /users/{userId}/fx/quote and { "fromCurrency": "KES", "toCurrency": "USD", "amount": 65000 }. Supply a fresh Idempotency-Key for this logical quote. Review sourceAmount, targetAmount, feeAmount, feeCurrency, rateConvention and expiresAt. The shared platform fee policy applies; configured rates and fees are authoritative, not an invented real-time provider rate. A quote touches no balances. Fees round to the nearest source minor unit; target credits round down to two decimal places so splitting conversions cannot create value. Quotes expose roundingMode: DOWN and calculationVersion: SOURCE_FEE_TARGET_FLOOR_V2. Refresh any unconsumed quote issued before this calculation policy.
Confirm the returned quoteId with POST /users/{userId}/fx/convert and a separate stable Idempotency-Key. Retry that same quote and key after an uncertain network outcome. Expired/consumed quotes and changed idempotency payloads return 409; missing rates fail instead of assuming parity. The source debit, target credit, fee ledger, traceable funding-origin transfer, audit and notification outbox are committed together. Native conversion currently supports currencies represented by the shared two-decimal wallet swap engine; digital assets and incompatible precision are excluded.
Sandbox operates on isolated virtual wallets. Live conversion remains disabled until identity, partner entitlements and currency funding/payout/reconciliation capabilities are approved. Existing untraceable balances are not relabelled as approved funding origin. A completed conversion receipt does not prove downstream webhook delivery; inspect delivery independently.
Partner master wallets use POST /fx/quote and POST /fx/convert with the same bodies, expiry and idempotency rules. Their funds and provenance remain under the master account; customer conversion never credits the master wallet. Live master FX also requires an active approved partner organization.
How do I fund and return actual currency balances?
Use POST /users/{userId}/deposit?walletContract=2 to transfer actual currency from firm to customer, and POST /users/{userId}/withdraw?walletContract=2 to return it. Send only amount and currency, with a unique Idempotency-Key. These are internal transfers, not bank payouts. Without the opt-in, the legacy USD behavior is unchanged.
Sandbox journey: top up virtual firm USD, quote/confirm firm USD-to-KES FX, transfer actual KES to the customer, quote/confirm customer KES-to-USD FX, then trade from USD. Return unused settled funds to the firm. Top-ups record virtual funding origin; legacy untraced money is never silently relabelled.
Trades and subscriptions still use USD. Convert beforehand; KES does not automatically fund a USD trade. Missing managed rates are rejected. Keep journals and FX fees grouped by actual currency. internalTransferAvailable is separate from external withdrawable.
Live native transfers require operational acceptance; native external payouts remain unavailable. The approved legacy USD payout workflow continues.
/users/{userId}/deposit
Deposit to sub-account
Full endpoint documentationcreateDepositNative opt-in: ?walletContract=2 transfers actual specified currency between firm and customer wallets. Body contains only amount and currency. Requires traceable funding origin and settled available funds. Live requires approved currency capabilities and partner policy. No FX or external bank payout occurs. The v2 response contains transferId, direction, currency, amount, sourceBalance, targetBalance, simulationOnly, and externalPayout=false. Legacy v1 behavior below remains unchanged. Credits USD from your master wallet into a sub-account's wallet. Your master wallet must have sufficient balance — check via GET /account. Managed FX model — pass a USD ledger amount (amount or amountUsd) or pass amount with a non-USD currency such as KES. MyStocks converts supported non-USD amounts into the USD ledger using the managed conversion terms from the FX quote endpoints; bulk rate export is disabled. Sandbox uses the same conversion model but virtual balances only. Missing FX rates and unsupported currencies are rejected. Pass Idempotency-Key to make deposit retries safe on network failure.
Parameters
walletContract1 | 2queryVersion 2 transfers actual currency without FX. Internal transfer only; body contains amount and currency.
Example: 1
userIdstringpathRequiredExample: usr_abc123
Headers
AuthorizationstringRequiredBearer sk_sandbox_<your_key> for sandbox requests.
Idempotency-KeystringheaderRequiredUnique string that makes write calls safe to retry on network failure. Required for money movement, trading, KYC, subscriptions, and webhook creation. Deduplicated for 24 hours -- duplicate keys return the cached response. HTTP 409 if a concurrent request with the same key is still in progress.
Example: dep_user42_1743152580
Request body
amountnumberUSD ledger amount to deposit, unless currency is a non-USD ISO code. If currency is non-USD, amount is interpreted in that currency and converted to USD by MyStocks managed FX.
Example: 500
amountUsdnumberExplicit USD ledger amount. Use this when you also include local display fields.
Example: 500
currencystringCurrency of amount. Defaults to USD. Non-USD amounts are converted with MyStocks managed FX.
Example: "KES"
notestringOptional description shown in the transaction history.
Example: "Mpesa STK push ref KE2482"
localAmountnumberEquivalent amount in the user local currency. Stored for audit and compliance only.
Example: 65000
localCurrencystringISO 4217 code for the local amount.
Example: "KES"
fxRatenumberLegacy partner-provided exchange rate. Omit this to use MyStocks managed FX.
Example: 130
Response fields
See the response format and API Reference for this endpoint.
Response codes
200Deposit processed.400Master wallet has insufficient balance.409Idempotency conflict./users/{userId}/withdraw
Withdraw from sub-account
Full endpoint documentationcreateWithdrawNative opt-in: ?walletContract=2 transfers actual specified currency between firm and customer wallets. Body contains only amount and currency. Requires traceable funding origin and settled available funds. Live requires approved currency capabilities and partner policy. No FX or external bank payout occurs. The v2 response contains transferId, direction, currency, amount, sourceBalance, targetBalance, simulationOnly, and externalPayout=false. Legacy v1 behavior below remains unchanged. Moves USD from a sub-account's wallet back to your master wallet. Use this when your user requests a cash-out. The funds are immediately available in your master wallet. Pass a USD ledger amount or a supported local amount + currency; non-USD values are converted with MyStocks managed FX. Sandbox uses the same conversion model but virtual balances only. Missing FX rates and unsupported currencies are rejected. Pass Idempotency-Key to make withdrawals safe to retry.
Parameters
walletContract1 | 2queryVersion 2 transfers actual currency without FX. Internal transfer only; body contains amount and currency.
Example: 1
userIdstringpathRequiredExample: usr_abc123
Headers
AuthorizationstringRequiredBearer sk_sandbox_<your_key> for sandbox requests.
Idempotency-KeystringheaderRequiredUnique string that makes write calls safe to retry on network failure. Required for money movement, trading, KYC, subscriptions, and webhook creation. Deduplicated for 24 hours -- duplicate keys return the cached response. HTTP 409 if a concurrent request with the same key is still in progress.
Example: dep_user42_1743152580
Request body
amountnumberUSD ledger amount to move, unless currency is a non-USD ISO code. If currency is non-USD, amount is interpreted in that currency and converted to USD by MyStocks managed FX.
Example: 200
amountUsdnumberExplicit USD ledger amount. Use this when you also include local cash-out display fields.
Example: 200
currencystringCurrency of amount. Defaults to USD. Non-USD amounts are converted with MyStocks managed FX.
Example: "KES"
notestringOptional description shown in transaction history.
localAmountnumberEquivalent in local currency for the audit trail.
Example: 26000
localCurrencystringISO 4217 code.
Example: "KES"
fxRatenumberLegacy partner-provided exchange rate. Omit this to use MyStocks managed FX.
Example: 130
Response fields
See the response format and API Reference for this endpoint.
Response codes
200Withdrawal processed.400Sub-account has insufficient wallet balance.How does an administrator manage currency access?
In /admin/partners, open the organization workspace and its overview. The currency workspace shows firm/customer cash by currency, policy entitlements, operational blockers and notification dispatch status. Finance-ledger permission is needed to read it. Release-management permission, recent sign-in, required MFA, a reason and explicit confirmation are required to change Live currency policy. Decisions write an audit record and queue a notification.
The global platformConfig/partnerCurrencyCapabilities record must contain verified funding, FX, payout and reconciliation acceptance for each Live currency; a partner cannot enable a missing rail by choosing a currency. The partner-specific partnerCurrencyPolicies/{partnerId} controls currencies, fxEnabled and nativeTransfersEnabled. No Live pilot is enabled by this release. USD trading and the legacy payout path remain separate.
Currency events wallet.converted, wallet.transferred and wallet.policy.updated enter a durable outbox. The secured existing events cron dispatches them to the partner stream and registered webhooks. DISPATCHED means handoff to that engine, not confirmed recipient delivery. Repeated dispatch uses the same eventId; consumers must deduplicate. Failed HTTP deliveries follow the existing webhook retry process.
The hosted and Sandbox Firm Balance pages provide actual-wallet selection, fee/expiry preview, explicit confirmation and native internal transfers. Capabilities shown in the browser do not override server authorization.
Rate freshness and source
Rates use local currency units per USD, with USD fixed at 1. Daily observations expire after 36 hours. Missing or expired non-USD rates block new conversions; a 60-second quote expiry does not renew its underlying rate. Quotes and new receipts record fxRateVersion, rateAsOf, source and convention. Currency equivalents are indicative; a completed ledger conversion does not prove external provider execution or treasury backing.
Daily indicative rates: Rates By Exchange Rate API. Bulk rate redistribution is disabled. Do not use the deprecated bulk endpoint to populate your own rate service.
Was this page useful?
Your signal helps us tighten partner onboarding docs.
Last updated on