Skip to content
MyStocks Developers
API v1

API v1

Current stable contract

Versioning policyRelease changelog
Sandbox console
Start building

Paper and Demo Trading

Use the MyStocks sandbox and integration test tools to validate trading workflows without moving real money.

MyStocks calls its non-production environment Sandbox. It is the equivalent of a paper/demo environment for partner integration testing, but it has its own key prefix and base URL.

SurfaceKeyBase URLPurpose
Sandbox environmentsk_sandbox_/api/sandbox/v1/partnerIsolated virtual accounts and accelerated trading workflows
Canonical deterministic test toolssk_sandbox_/api/sandbox/v1/partner/test-tools/*Simulated order, wallet, holding, execution, and webhook scenarios for new integrations
Restricted legacy compatibilitypk_live_/api/v1/partner/test-tools/*Virtual-event testing against approved live webhook configuration; never production trading

The production-base compatibility surface is not the isolated sandbox. Deprecated /sandbox/* aliases remain only for existing integrations and must not be used in new examples.

What to test

  • account creation and auto-registration;
  • deposits, withdrawals, and idempotent retries;
  • KYC-required and frozen-account behavior;
  • quote expiry and mismatched quote rejection;
  • market, limit, stop, and cancellation lifecycles;
  • insufficient funds and holdings errors;
  • webhook signatures, duplicates, ordering, and retry leases;
  • SSE reconnect and Last-Event-ID replay;
  • daily reconciliation and statement generation.

Sandbox parity boundaries

The sandbox mirrors the partner-facing treasury and revenue workflows needed for product design: GET/PATCH/POST /float, GET/PATCH /pricing, and master or sub-account GET /tax-lots and GET /gains. Pricing and treasury updates are virtual and never alter production settings. Tax lots and gains are derived from sandbox instant-fill orders using the same FIFO reporting model as production.

The following controls remain production-only and appear as non-executable reference entries in the API Tester: OAuth client credentials, enterprise security policy, go-live certification, reconciliation packs, OMS execution reports, SSE streaming, and resting-order replacement. OpenAPI operations declare this boundary with x-sandbox: false, and CI fails if the route, specification, and tester disagree.

Deterministic execution certification

List a customer's simulated orders with their actual sub-account ID:

Replace usr_abc123 with the ID returned by POST /users. Omitting subAccountId in sandbox returns 400 MISSING_PARAM; the production compatibility route treats it as an optional filter.

POST

/users

Create sub-account

Full endpoint documentation
Sandbox availablecreateUsers

Creates a new sub-account. Each sub-account has an isolated USD wallet, portfolio, and order book. The externalId is your own reference — it must be unique per partner and is used in all subsequent calls. Sub-accounts start with a walletBalance of $0 — fund them via POST /users/{userId}/deposit.

Parameters

This endpoint has no path or query parameters.

Headers

AuthorizationstringRequired

Bearer sk_sandbox_<your_key> for sandbox requests.

Idempotency-KeystringheaderRequired

Unique 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

externalIdstringRequired

Your internal user identifier. Must be unique per partner.

Example: "usr_8821"

displayNamestring

Full name shown in the MyStocks admin console.

Example: "Alice K."

emailstring

Email address for the sub-account user.

Example: "alice@yourapp.com"

phonestring

Customer phone number in international format when available.

Example: "+254712345678"

countrystring

ISO 3166-1 alpha-2 country code used for onboarding and compliance context.

Example: "KE"

Response fields

subAccountIdstring

MyStocks internal ID. Use in /users/{userId}/... paths.

Example: "usr_abc123"

userIdstring

Compatible alias of subAccountId. Use subAccountId as the canonical stored identifier.

externalIdstring

Your own user identifier as set at creation.

Example: "usr_8821"

displayNamestring | null

Example: "Alice K."

emailstring | null

Example: "alice@yourapp.com"

kycStatusNONE | PENDING | VERIFIED | REJECTED

VERIFIED required for trading and asset subscriptions. REJECTED remains gated until re-asserted.

kycLevelNONE | BASIC | FULL

BASIC = ID verified. FULL = enhanced due diligence.

kycRejectionCodestring | null

Stable rejection code when kycStatus is REJECTED.

kycRejectionReasonstring | null

Customer-safe remediation guidance when kycStatus is REJECTED.

kycReviewedAtstring | null

Timestamp of the rejection decision.

statusactive | frozen | closed
walletBalancenumber

Current USD wallet balance.

Example: 1250

Response codes

201Sub-account created.
409externalId already exists.

Expected restrictions and test outcomes

RequestExpected result and correct test setup
POST /tradeMaster-account trading is unavailable in sandbox and returns 422. Use POST /users/{userId}/trade for a successful virtual trade.
POST /payoutSandbox returns 422 because virtual funds cannot be paid out. GET /payout remains available.
Enterprise, team, security, approvals, sessions, and support operationsProduction-only operations are marked x-sandbox: false and disabled for sandbox keys in the API Tester. An authenticated request to an absent sandbox route returns 404 SANDBOX_UNSUPPORTED.
Schema discoveryFetch OpenAPI JSON or OpenAPI YAML from the site root, without the sandbox API prefix or credentials.
Corporate-action electionRead the action first and select a valid option. See election options.

Exclude production-only operations from sandbox happy-path coverage. Test expected restrictions separately, asserting the response status and error payload. A correct rejection is a passing negative test, not a successful trade or payout. Unknown paths also use the sandbox fallback, so a 404 alone does not establish that the requested operation exists in production.

For replay tests, use a supported mutation, the same idempotency key and identical body (including clientOrderId), and verify that the wallet/order changes only once. Test changed-body rejection separately.

Advancing a deterministic scenario

Use POST /test-tools/trade on the sandbox base URL to create an isolated lifecycle scenario. Set outcome to PENDING, FILL, PARTIAL_FILL, REJECT, CANCEL, or FAIL_SETTLEMENT and provide a deterministic unitPriceUsd. The simulator updates virtual wallet reservations, holdings, execution records, and the same signed order webhooks used by production.

Advance pending and partially filled scenarios without MyStocks staff:

Repeat with FILL for the remainder, then SETTLE. Use REJECT, CANCEL, or FAIL_SETTLEMENT to certify terminal failure handling. Every instruction requires an idempotency key so retries cannot apply the same wallet or holding movement twice.

Sandbox trades may settle instantly and do not reproduce every production timing characteristic. Always run the go-live certification checks before requesting production access.

POST

/test-tools/trade

Submit test trade

Full endpoint documentation
Sandbox availablecreateTestToolsTrade

Places a deterministic test trade without hitting a real exchange. Recommended: use the sandbox base URL with an sk_sandbox_ key. Set outcome to PENDING, FILL, PARTIAL_FILL, REJECT, CANCEL, or FAIL_SETTLEMENT; sandbox wallet reservations, holdings, execution records, and webhooks are updated consistently. A pending or partially filled order can be advanced with PATCH /test-tools/orders/{orderId}. The production base retains the older live-key integration tool for compatibility: Cheat codes for deterministic outcomes: | Quantity | Outcome | |----------|---------| | 100 | Auto-fills immediately (triggers trade.settled) | | 999 | Auto-rejects immediately (triggers trade.rejected) | | Any other | Enters PENDING state — use admin panel to settle | Formerly POST /sandbox/trade, which remains a supported alias.

Parameters

This endpoint has no path or query parameters.

Headers

AuthorizationstringRequired

Bearer sk_sandbox_<your_key> for sandbox requests.

Idempotency-KeystringheaderRequired

Unique 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

symbolstringRequired

Stock symbol (exchange-qualified or bare ticker).

Example: "SCOM.KE"

typeBUY | SELLRequired

Order direction.

quantitynumberRequired

Number of shares. On the sandbox base URL, use outcome for deterministic behavior. The legacy live-key test tool still accepts 100 = instant fill and 999 = instant rejection.

Example: 10

subAccountIdstring

Sub-account to trade on. BUY checks wallet and escrows; SELL checks held units. Omit to bypass all checks.

notestring

Free-text note shown to the admin for labelling test scenarios.

unitPriceUsdnumber

Deterministic execution price used by the isolated sandbox simulator.

outcomePENDING | FILL | PARTIAL_FILL | REJECT | CANCEL | FAIL_SETTLEMENT

Explicit isolated-sandbox lifecycle outcome. Ignored by the legacy live-key test tool.

partialQuantitynumber

Required when outcome is PARTIAL_FILL; must be less than quantity.

Response fields

orderIdstring

Unique order ID. Use to poll status or cancel.

Example: "ord_abc123xyz"

symbolstring

Example: "SCOM.KE"

namestring

Example: "Safaricom PLC"

exchangestring

Example: "NSE"

orderTypeMARKET | LIMIT | STOP | STOP_LIMIT

Order type. MARKET orders are quote-gated; LIMIT/STOP/STOP_LIMIT rest as WORKING until triggered.

Example: "LIMIT"

limitPricenumber | null

Limit price in the stock's local currency (LIMIT / STOP_LIMIT only).

stopPricenumber | null

Stop price in the stock's local currency (STOP / STOP_LIMIT only).

cancelableboolean

Whether the order can be cancelled now (DELETE). True while PENDING or WORKING.

replaceableboolean

Whether the order can be modified now (PATCH). True only for a WORKING resting order.

typeBUY | SELL
statusPENDING | WORKING | PARTIALLY_FILLED | PROCESSING | COMPLETED | FILLED | REJECTED | CANCELLED | EXPIRED

PENDING after submission. WORKING = resting LIMIT/STOP/STOP_LIMIT order waiting for its trigger price (becomes PENDING on trigger). PARTIALLY_FILLED and FILLED are the canonical execution states. REJECTED, CANCELLED, and EXPIRED are terminal outcomes. PROCESSING is a legacy transition alias and COMPLETED is a v1 compatibility alias of FILLED; neither represents custody settlement. Settlement is tracked separately as PENDING, SETTLED, or FAILED.

quoteIdstring | null

Pre-trade quote ID consumed by this order.

quoteExpiresAtstring | null
quoteUsdPricenumber | null
clientOrderIdstring | null

Partner-supplied order reference.

omsStatusstring | null

Canonical OMS lifecycle status.

timeInForcestring | null
filledQuantitynumber
remainingQuantitynumber
averageFillPricenumber | null
executionVenuestring | null
lastExecutionReportIdstring | null
executionSlaunion

Live execution SLA clock for orders that have entered the PENDING dealing-desk queue. Null while a resting order is still WORKING.

quantityinteger
priceAtOrdernumber

Stock price in local currency at submission.

usdPriceAtOrdernumber

USD equivalent of priceAtOrder.

fxRateAtOrdernumber | null

Managed FX rate used for local-to-USD conversion at order submission.

fxSourcestring | null
fxRateAsOfstring | null
fxConversionunion
feenumber

Total fee in USD (base + partner markup).

baseFeenumber

MyStocks base broker fee (0.75% of gross).

partnerMarkupFeenumber

Your markup fee component. Zero if markupBps = 0.

totalAmountnumber

BUY: gross + fee. SELL: gross - fee.

currencystring

Example: "USD"

localCurrencystring

Example: "KES"

rejectionCodestring | null

Structured rejection code. Populated when status = REJECTED.

rejectionReasonstring | null

Free-text rejection detail provided by the MyStocks internal-book execution service. Populated when status = REJECTED.

cancelledAtstring | null
settledAtstring | null

Set when custody settlement completes; independent of the canonical FILLED execution state. Legacy completed records that pre-date this field return their immutable completion/creation timestamp as compatibility evidence.

createdAtstring
canonicalStatusPENDING | WORKING | PARTIALLY_FILLED | FILLED | REJECTED | CANCELLED | EXPIRED
settlementStatusPENDING | SETTLED | FAILED
orderLifecyclestring | null

EXPIRED for expiry; expiry is not a rejection.

cancelReasonstring | null

Response codes

201Test order placed.

Existing simulation tools and boundaries

Use POST /test-tools/trade with outcome set to PENDING, FILL, PARTIAL_FILL, REJECT, CANCEL or FAIL_SETTLEMENT. Advance existing orders with PATCH /test-tools/orders/{orderId} using the documented actions. The proposed X-Sandbox-Execution-Outcome headers are not the contract. Corporate-action cash simulations and market-session override endpoints are not currently available.

Actual currency wallets use ?walletContract=2; obtain an FX quote before confirming its quoteId with a stable idempotency key. Trades remain USD-settled. This testing flow does not prove broker settlement, FIX connectivity, hedging or regulatory compliance.

How do I test expiry and a specific rejection?

Create a pending order with the existing test tools, then PATCH its order ID. Send subAccountId with every transition and an Idempotency-Key. Use EXPIRE to close the unfilled remainder, release only unused reservations and preserve earlier fills. The event is order.cancelled, with canonicalStatus: EXPIRED, orderLifecycle: EXPIRED and cancelReason: SIMULATED_EXPIRY. Expiry is not a rejection.

curl -X PATCH "$API_BASE/test-tools/orders/$ORDER_ID" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{"subAccountId":"customer-1","action":"EXPIRE"}'

For REJECT, optionally supply rejectionCode: INSUFFICIENT_FUNDS, KYC_REQUIRED, MARKET_CLOSED, COMPLIANCE_HOLD, TECHNICAL_ISSUE or OTHER (the default). The code is saved on the order and included in order.rejected. A code on another action is rejected. Frozen accounts cannot receive any new fills, including a fill followed by simulated settlement failure. They can still cancel, expire, reject or mark existing filled orders settled. These are deterministic virtual tests, not an exchange execution or settlement guarantee.

Corporate-action and market-session simulations require a separate tenant-isolated scenario design; they are not available through these actions or arbitrary request headers.

Was this page useful?

Your signal helps us tighten partner onboarding docs.

Send note

Last updated on

On this page