Skip to content
MyStocks Developers
API v1

API v1

Current stable contract

Versioning policyRelease changelog
Sandbox console
Start building

Quick Start

Go from zero to your first executed trade — register, create a sub-account, deposit, quote, and trade — all against the MyStocks sandbox with no approval required.

Go from zero to your first executed trade — all against the sandbox, no approval required.

Sandbox path

  1. 1

    Register

    Get a sandbox key from the partner portal session.

    POST /register
  2. 2

    Create user

    Create an isolated wallet for your end-user.

    POST /users
  3. 3

    Deposit

    Credit the user wallet with partner-managed FX details.

    POST /users/{userId}/deposit
  4. 4

    Optional: assert KYC

    Exercise the production-style KYC assertion flow; sandbox trading does not require it.

    POST /users/{userId}/kyc
  5. 5

    Quote

    Fetch a single-use quoteId that expires after 60 seconds.

    GET /quote/{symbol}
  6. 6

    Trade

    Submit the order with the matching quoteId and idempotency key.

    POST /users/{userId}/trade

Register and get your API key

Try in API Tester

Sign in (or sign up) on the partner portal first — /register authenticates with your Firebase ID token from that session, not an API key. The easiest path is the portal's "Create Key" or "Rotate Key" button, which calls this endpoint for you. Rotation is explicit because calling it again invalidates the previous sandbox key.

curl -X POST https://mystocks.africa/api/sandbox/v1/register \
  -H "Authorization: Bearer <firebase-id-token>"
{ "apiKey": "sk_sandbox_xxxxxxxxxxxxxxxx", "walletBalance": 100000, "currency": "USD" }

Create a sub-account for your end-user

Try in API Tester

Each of your end-users gets an isolated wallet. Alternatively use POST /api/sandbox/v1/partner/auto-register (idempotent — safe to call on every login).

bash
curl -X POST https://mystocks.africa/api/sandbox/v1/partner/users \
-H "Authorization: Bearer sk_sandbox_<your_key>" \
-H "Content-Type: application/json" \
-d '{ "externalId": "user_42", "displayName": "Jane Doe", "email": "jane@example.com" }'
{
  "subAccountId": "usr_xxxxxxxxxxxx",
  "externalId": "user_42",
  "displayName": "Jane Doe",
  "kycStatus": "NONE",
  "status": "active",
  "wallet": { "currency": "USD", "balance": 0 }
}

Deposit funds

Try in API Tester

After collecting your user's local-currency payment, send its local amount and currency. MyStocks uses managed FX to calculate the USD ledger credit and returns the conversion metadata.

curl -X POST https://mystocks.africa/api/sandbox/v1/partner/users/usr_xxxxxxxxxxxx/deposit \
  -H "Authorization: Bearer sk_sandbox_<your_key>" \
  -H "Idempotency-Key: dep_user42_001" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 65000, "currency": "KES", "note": "Mpesa ref mpesa_QHJ29SK" }'
{ "message": "Deposit successful.", "subAccountId": "usr_xxxxxxxxxxxx", "amount": 500, "currency": "USD", "fxRate": 130, "fxSource": "MYSTOCKS_MANAGED_FX", "newSubBalance": 500, "newMasterBalance": 99500 }

Optional: assert KYC

Try in API Tester

You run KYC in your own onboarding flow, then assert the result. The sandbox permits synthetic customers to trade without KYC so a new integration can reach its first fill immediately. Production still requires VERIFIED; an unverified production sub-account receives 403 KYC_REQUIRED. Use this optional sandbox step, or the KYC_REQUIRED simulation scenario in the API Tester, to exercise your production error and recovery UX.

curl -X POST https://mystocks.africa/api/sandbox/v1/partner/users/usr_xxxxxxxxxxxx/kyc \
  -H "Authorization: Bearer sk_sandbox_<your_key>" \
  -H "Idempotency-Key: kyc_user42_001" \
  -H "Content-Type: application/json" \
  -d '{ "status": "VERIFIED", "level": "BASIC", "reference": "kyc_provider_ref_123" }'
{ "message": "KYC status updated.", "subAccountId": "usr_xxxxxxxxxxxx", "kycStatus": "VERIFIED", "kycLevel": "BASIC" }

Get a quote (returns the required quoteId)

Try in API Tester

Every trade — sandbox and production — starts with a quote. It returns the latest delayed price observation, the full fee breakdown, and a single-use quoteId that expires after 60 seconds.

curl "https://mystocks.africa/api/sandbox/v1/partner/quote/SCOM.KE?type=BUY&quantity=1000&subAccountId=usr_xxxxxxxxxxxx" \
  -H "Authorization: Bearer sk_sandbox_<your_key>"
{
  "quoteId": "qt_9f2c81d4b7a3",
  "quoteExpiresAt": "2026-07-07T09:46:00Z",
  "quoteTtlSeconds": 60,
  "symbol": "SCOM.KE",
  "name": "Safaricom PLC",
  "exchange": "NSE",
  "currency": "KES",
  "type": "BUY",
  "quantity": 1000,
  "localPrice": 16.5,
  "usdPrice": 0.0126,
  "gross": 12.6,
  "fee": 0.09,
  "totalCost": 12.69,
  "sufficientFunds": true
}

Place a trade on behalf of the sub-account

Try in API Tester

Pass the quoteId from the previous step within 60 seconds. You can use the full exchange-qualified symbol (SCOM.KE) or just the bare ticker (SCOM) — the API resolves it automatically.

bash
curl -X POST https://mystocks.africa/api/sandbox/v1/partner/users/usr_xxxxxxxxxxxx/trade \
-H "Authorization: Bearer sk_sandbox_<your_key>" \
-H "Idempotency-Key: trade_user42_001" \
-H "Content-Type: application/json" \
-d '{ "symbol": "SCOM.KE", "type": "BUY", "quantity": 1000, "quoteId": "qt_9f2c81d4b7a3" }'
{
  "status": "FILLED",
  "orderId": "ord_xxxxxxxxxxxx",
  "subAccountId": "usr_xxxxxxxxxxxx",
  "type": "BUY",
  "symbol": "SCOM.KE",
  "quantity": 1000,
  "totalCost": 12.69,
  "newSubBalance": 487.31,
  "note": "Order filled instantly in sandbox. Live production trades are PENDING until filled or rejected."
}

Check order history (or use webhooks)

curl "https://mystocks.africa/api/sandbox/v1/partner/users/usr_xxxxxxxxxxxx/orders?limit=1" \
  -H "Authorization: Bearer sk_sandbox_<your_key>"

In sandbox, sub-account trades settle instantly (FILLED) with no admin queue. In production, status progresses PENDING → FILLED (or REJECTED) through the execution flow during exchange hours. v1 may emit legacy COMPLETED as an alias of FILLED.

Settlement timeline — production

PhaseTimelineNotes
MyStocks live fill targetWithin 5 minutesMarket orders submitted during exchange hours. Outside hours → queued for next session.
Exchange settlement (most exchanges)T+3 business daysNSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, DSE, USE, MSE, CSE, SEM — title transfers 3 business days after execution.
Exchange settlement (EGX)T+2 business daysEgyptian Exchange — title transfers 2 business days after execution.

The MyStocks execution target is when your PENDING order becomes FILLED — this triggers the order.filled webhook and credits shares/proceeds to the wallet. The exchange settlement cycle (T+2/T+3) governs when the underlying title and custody transfer through the exchange depository; it does not affect wallet balances in the API. Full per-exchange data is at GET /api/v1/partner/market/settlement.

Order lifecycle

POST /trade
submit order
PENDING
funds escrowed
internal book fills automatically · target 5 min
FILLED
order.filled

A PENDING order can instead terminate as REJECTED (BUY escrow refunded · order.rejected) or, while still PENDING, be CANCELLED by your DELETE (order.cancelled).

Sandbox only: orders skip the escrow wait and resolve immediately as FILLED. A live order can be cancelled while it is still PENDING (or WORKING, for resting limit/stop orders) — once it reaches a terminal state (FILLED, REJECTED, CANCELLED, or EXPIRED) the DELETE returns HTTP 409.

Cancelling a PENDING order

While an order is still PENDING, cancel it with a DELETE. For BUY orders the escrowed funds are refunded atomically; SELL orders carry no wallet impact so cancellation is immediate. Once an order has filled (FILLED) or otherwise reached a terminal state (REJECTED, CANCELLED, EXPIRED) it can no longer be cancelled (HTTP 409). A successful cancellation fires an order.cancelled webhook so downstream systems react without polling.

# Cancel a sub-account order
curl -X DELETE "https://mystocks.africa/api/v1/partner/users/usr_xxxxxxxxxxxx/orders/ord_xxxxxxxxxxxx" \
  -H "Authorization: Bearer pk_live_<your_key>" \
  -H "Idempotency-Key: cancel-subaccount-order-ord_xxxxxxxxxxxx"
 
# Cancel a master-account order
curl -X DELETE "https://mystocks.africa/api/v1/partner/orders/ord_xxxxxxxxxxxx" \
  -H "Authorization: Bearer pk_live_<your_key>" \
  -H "Idempotency-Key: cancel-master-order-ord_xxxxxxxxxxxx"

Modifying a resting order (replace)

A resting LIMIT/STOP/STOP_LIMIT order sits in status WORKING until its price triggers. While WORKING you can modify its limitPrice, stopPrice, and/or quantity with a PATCH — the order keeps the same orderId, and the BUY escrow or SELL unit reservation is adjusted by the delta atomically. Increasing a BUY escrow beyond the available balance returns 400 INSUFFICIENT_FUNDS; the replaceable field on the order tells you when a modify is allowed. Market orders cannot be modified — cancel and re-submit instead. Requires an Idempotency-Key.

# Re-price and re-size a WORKING resting order (same orderId)
curl -X PATCH "https://mystocks.africa/api/v1/partner/users/usr_xxxxxxxxxxxx/orders/ord_xxxxxxxxxxxx" \
  -H "Authorization: Bearer pk_live_<your_key>" \
  -H "Idempotency-Key: replace_ord_xxxxxxxxxxxx_1" \
  -H "Content-Type: application/json" \
  -d '{ "limitPrice": 21.5, "quantity": 150 }'

A successful modify fires an order.replaced webhook and writes a REPLACE execution report.

Skip polling — use webhooks. Register a webhook URL to receive order.filled, order.rejected, and order.cancelled events in real time. See Webhooks, and the Going Live checklist when you're ready to switch to production.

Was this page useful?

Your signal helps us tighten partner onboarding docs.

Send note

Last updated on