Skip to content
MyStocks Developers
API v1

API v1

Current stable contract

Versioning policyRelease changelog
Sandbox console
Reference

Rate Limits

MyStocks Partner API

Per-API-key, per-minute rate limits by tier (Sandbox 300, Starter 100, Growth 500, Enterprise 2,000). Both environments expose X-RateLimit headers; a sliding 1-minute window returns 429 with Retry-After.

Explore endpoints

3 of 3
GET

/stocks

List stocks (NSE, NGX, JSE, GSE, BRVM, etc.)

Full endpoint documentation
Sandbox availablelistStocks

Returns the full tradeable stock and ETF universe across all supported African exchanges. Provides exchange-supplied, 15-minute-delayed stock price data. The API refresh target is 900 seconds during the published coverage window. These are not real-time prices. MyStocks holds full data rights for every supported exchange. - Nairobi Stock Exchange (NSE) in Kenya (KES) - Nigerian Exchange Group (NGX) in Nigeria (NGN) - Johannesburg Stock Exchange (JSE) in South Africa (ZAR) - Ghana Stock Exchange (GSE) in Ghana (GHS) - BRVM serving 8 West African French-speaking countries (XOF) - LuSE (Zambia), USE (Uganda), BSE (Botswana), DSE (Tanzania), ZSE (Zimbabwe), EGX (Egypt), MSE (Malawi), CSE (Morocco), and SEM (Mauritius). Filter by exchange, sector, asset type, or free-text search. Results are cursor-paginated to keep catalogue responses fast as market coverage grows. Use nextCursor while hasMore is true.

Parameters

exchangestringquery

Filter by exchange code. One of: NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX, DSE, USE, MSE, CSE, SEM.

Example: NSE

sectorstringquery

Filter by GICS sector name.

Example: Telecommunications

assetTypeSTOCK | ETFquery

Filter to stocks or ETFs.

searchstringquery

Free-text match against symbol or company name.

Example: safaricom

limitintegerquery

Page size. Defaults to 50 and is capped at 200.

Example: 50

cursorstringquery

Continuation cursor returned as nextCursor by the previous page.

Headers

AuthorizationstringRequired

Bearer sk_sandbox_<your_key> for sandbox requests.

Response fields

stocksobject[]
stocks[].idstring

Unique stock identifier.

Example: "SCOM.KE"

stocks[].symbolstring

Exchange-qualified ticker symbol.

Example: "SCOM.KE"

stocks[].namestring

Full company name.

Example: "Safaricom PLC"

stocks[].slugstring | null

URL-safe identifier. Interchangeable with symbol.

Example: "safaricom"

stocks[].exchangestring

Exchange code: NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX, DSE, USE, MSE, CSE, SEM.

Example: "NSE"

stocks[].exchangeMicstring | null

ISO 10383 operating Market Identifier Code for the listing exchange.

Example: "XNAI"

stocks[].currencystring

Local trading currency.

Example: "KES"

stocks[].sectorstring | null

Example: "Telecommunications"

stocks[].assetTypeSTOCK | ETF
stocks[].listingStatusACTIVE | SUSPENDED | DELISTED
stocks[].pricenumber | null

Latest price in local currency.

Example: 16.5

stocks[].usdPricenumber | null

Latest price converted to USD at live spot rate.

Example: 0.1274

stocks[].changenumber | null

Absolute price change from previous close.

Example: 0.25

stocks[].changePctnumber | null

Percentage price change from previous close.

Example: 1.54

stocks[].opennumber | null

Session opening price in local currency.

stocks[].dayHighnumber | null

Intraday high in local currency.

stocks[].dayLownumber | null

Intraday low in local currency.

stocks[].volumeinteger | null

Current session trading volume.

stocks[].previousClosenumber | null

Prior session closing price.

stocks[].logoUrlstring | null

Self-hosted SVG logo. Safe to use as img src.

Example: "https://mystocks.africa/logos/scom-ke.svg"

stocks[].lastPriceUpdatestring | null
stocks[].marketStatusstring

Exchange state at quote time: OPEN or CLOSED. Present on /market/quotes and /market/movers responses.

stocks[].bidnumber | null

Best bid. Currently always null (no order-book depth in the data source).

stocks[].asknumber | null

Best ask. Currently always null (no order-book depth in the data source).

stocks[].dataFreshnessSecondsinteger | null

Seconds since the last price update. Present on /market/quotes and /market/movers responses.

stocks[].asOfstring | null

Timestamp of the exchange observation — NOT when the API answered. African equity data is 15-minute delayed and the API refresh target is 900 seconds, so display and evaluate freshness against this value.

stocks[].staleboolean

True when the price is older than two refresh intervals (30 minutes). The feed for this symbol is genuinely behind — do not trust the price for pricing a trade.

stocks[].dataQualityobject

The published market-data freshness contract. African equity quotes are supplied by the exchanges on a 15-minute-delayed basis, not in real time. MyStocks holds full exchange data rights. The API refresh target is 900 seconds during the published coverage window, with an end-of-day pass after market close.

countinteger

Number of stocks in this page.

totalCountinteger | null

Exact matching count when known; null for bounded cursor scans.

hasMoreboolean
nextCursorstring

Response codes

200List of stocks.

Limits are applied per API key, per minute. Every response includes rate-limit headers so you can track consumption and back off gracefully.

TierRequests / minConcurrent streamsNotes
Sandbox300Not enforced by the current Sandbox handlerDefault Sandbox request budget; check response headers for applied limits.
Starter1002Default production Starter budget.
Growth5005Available on the Growth plan. Contact us to upgrade.
Enterprise2,00020Custom limits above 2,000 req/min — contact partnerships@mystocks.africa.

The limit is keyed to your partner identity, not to a single credential: a full key and all of its data keys share one bucket, and the limit survives a key rotation.

Concurrent stream limit

Production GET /stream (Server-Sent Events) is capped at the concurrent streams shown above. The Sandbox stream handler currently does not enforce that production concurrency cap; this is not a promise of unlimited capacity. Opening more returns 429 RATE_LIMITED with activeConnections and limit in the error body, rather than opening a stream that immediately dies.

A stream holds its slot for as long as it is receiving heartbeats. If a connection drops without a clean close, its slot frees itself automatically within ~45 seconds — so a reconnect loop cannot permanently exhaust your own limit. One stream carries every event type; you do not need a connection per symbol or per sub-account.

Rate-limit headers

Sent on every authenticated sandbox and production response.

X-RateLimit-Limit: 100         # your tier's request ceiling per minute
X-RateLimit-Remaining: 43      # requests remaining in the current window
X-RateLimit-Reset: 1751400060  # Unix timestamp (seconds) when the window resets
Retry-After: 17                # seconds until reset (only set on 429 responses)

The window is a 1-minute sliding window. Exceeding the limit returns 429 immediately — requests are not queued or delayed. Use Retry-After (seconds) or X-RateLimit-Reset (epoch) to know when to retry.

// 429 response body
{ "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Tier: starter (100 req/min). Resets in 17s." } }

Backoff example (Node.js)

async function fetchWithBackoff(url, options, retries = 3) {
  const res = await fetch(url, options);
  if (res.status === 429 && retries > 0) {
    const reset = Number(res.headers.get('X-RateLimit-Reset'));
    const delay = Math.max(reset * 1000 - Date.now(), 1000);
    await new Promise((r) => setTimeout(r, delay));
    return fetchWithBackoff(url, options, retries - 1);
  }
  return res;
}
  • Read endpoints (GET /stocks, GET /quote/{symbol}) are cheaper to cache locally — avoid polling per user request.
    • Batch sub-account operations: list all orders once per minute rather than one request per user.
    • Webhook callbacks do not count toward your rate limit.
    • Rate limits apply to sandbox and production keys independently.

Was this page useful?

Your signal helps us tighten partner onboarding docs.

Send note

Last updated on

On this page