Official Python SDK for SAHMK — Saudi market data, financials, and real-time market APIs.
SAHMK Python SDK
Official distribution: GitHub (sahmk-sa) and PyPI only. Do not download binaries from third-party forks.
Official Python SDK for Sahmk — Saudi market data and richer market workflows for developers.
Use one client for live Tadawul quotes, market-level insights, company/fundamental data, financials, events, and historical series.
Features
- Real-time quotes for 350+ Tadawul stocks
- Batch quotes for up to 50 symbols per request
- Historical OHLCV data with date-range support (
1d,1w,1m,30m,60m) - Market overview with index scoping (
TASI/NOMU) - Market depth order book snapshots (entitlement-gated)
- Live trades recent prints and real-time tape (Pro+)
- Company directory endpoint for symbol discovery
- Company/fundamental data (plan-dependent fields)
- Financials, dividends, and events endpoints (by plan)
- WebSocket streaming for quotes, depth, and trades (Pro+/entitled)
Installation
pip install sahmk
For local development:
git clone https://github.com/sahmk-sa/sahmk-python.git
cd sahmk-python
pip install -r requirements.txt
Security
- Use environment variables for API keys (recommended:
SAHMKAPIKEY). - Never commit API keys to source control, notebooks, or logs.
- If a key is exposed, rotate it immediately from your Sahmk dashboard.
Quick Start
import os
from sahmk import SahmkClient
client = SahmkClient(os.environ["SAHMKAPIKEY"])
quote = client.quote("2222") print(f"{quote['nameen']}: {quote['price']} SAR ({quote['changepercent']}%)")
market = client.market_summary(index="TASI") print(f"TASI: {market['indexvalue']} ({market['indexchange_percent']}%)")
Batch quotes are Starter+ plan.
for q in client.quotes(["2222", "1120", "7010"])["quotes"]:
print(f"{q['symbol']}: {q['price']}")
Identifier Resolution (Quotes)
quote() and quotes() accept either traditional symbols or resolvable identifiers:
- Symbol:
"2222" - Arabic company name:
"أرامكو السعودية" - English company name/alias:
"Aramco"
identifiers=..., then automatically falls back to
legacy symbols=... when connected to older backends.
q1 = client.quote("2222") # classic symbol usage
q2 = client.quote("أرامكو السعودية") # Arabic identifier
q3 = client.quote("Aramco") # English alias
batch = client.quotes(["2222", "الراجحي", "SABIC"]) for q in batch.quotes: print(q.requested_identifier, "=>", q.symbol)
if batch.ambiguous: print("Ambiguous:", batch.ambiguous) if batch.unknown: print("Unknown:", batch.unknown)
When the backend returns resolution metadata, it is exposed on typed objects:
quote = client.quote("Aramco")
print(quote.requested_identifier) # Aramco
print(quote.resolved_symbol) # 2222
print(quote.resolution.matched_by) # alias (if provided by API)
Company Directory / Symbol Discovery
Use companies() as the canonical symbol-discovery path before calling quote() or company().
# Search by symbol or name
directory = client.companies(search="aram")
for row in directory["results"]:
print(row["symbol"], row.get("name_en") or row.get("name"))
# Filter by market (TASI / NOMU, NOMUC alias is accepted)
nomu_companies = client.companies(market="NOMUC", limit=20)
print(nomu_companies["count"])
# Pagination loop with offset
offset = 0
page_size = 100
while True: page = client.companies(limit=page_size, offset=offset) for company in page["results"]: print(company["symbol"])
offset += page_size if offset >= page["total"]: break
Recommended flow:
- Discover valid symbols with
companies(). - Call
quote(symbol)/company(symbol)with a validated symbol.
Production Reliability
- The client retries transient failures: HTTP 429 and 5xx errors.
- Defaults:
retries=3,backoff_factor=0.5(0.5s, 1s, 2s). - Invalid symbols, authentication failures, and plan-access errors are not retryable.
from sahmk import SahmkClient
client = SahmkClient("yourapikey", retries=3, backoff_factor=0.5)
Plan Behavior
Some methods are plan-gated (for example quotes, historical, financials, dividends, events). When your plan does not include an endpoint, the API returns an error response (not retried automatically).
Historical data availability is plan-based:
- Free: no historical access
- Starter:
1d,1w,1m - Pro: Starter intervals +
60mup to 90 days - Business: Starter intervals +
30mup to 6 months +60mup to 1 year - Enterprise: Business defaults plus custom retention/interval/delivery by agreement
403 PLAN_LIMIT.
The SDK does not hard-block these requests client-side; it passes the API response through.
CLI Quick Start
export SAHMKAPIKEY="yourapikey"
sahmk quote 2222
sahmk market summary --index NOMU
sahmk market gainers --limit 5 --index NOMUC
sahmk historical 2222 --from 2026-01-01 --to 2026-01-28
sahmk historical 2222 --from 2026-01-01 --to 2026-01-03 --interval 60m
sahmk company 2222
sahmk financials 2222
sahmk ratios 2222 --history latest --period annual --metrics core
sahmk compare 2222,1120 --metrics extended
sahmk dividends 2222
sahmk events --symbol 2222 --limit 5
sahmk depth 2222 --levels 5
sahmk trades 2222 --limit 20
sahmk stream 2222,1120
sahmk stream-depth 2222,1120 --levels 5
sahmk stream-trades 2222,1120
You can also pass the key directly:
sahmk quote 2222 --api-key yourapikey
Typed Responses
Most methods return typed objects with IDE autocomplete while preserving dict-style access. Analytics methods (ratios, compare) return raw API dict responses to match the production contract exactly.
quote = client.quote("2222")
print(quote.price)
print(quote.liquidity.net_value)
Backwards-compatible dict access
print(quote["price"])
print(quote.get("volume"))
print(quote.raw)
Financials & Analytics
client.financials("1120", history="3y", result="latest")
client.ratios("1120")
client.ratios("1120", history="5y", period="quarterly", metrics="extended")
client.compare(["1120", "1180", "1010"])
client.compare(["1120", "1180", "1010", "2222"], metrics="extended")
Financials responses no longer include meta. Existing financial statement sections (incomestatements, balancesheets, cash_flows) are unchanged.
Analytics meta remains minimal and includes only:
periodmetricswarnings
Market Index Scoping
Supported values:
TASINOMUNOMUCalias (normalized toNOMU)
summary = client.market_summary(index="NOMUC")
print(summary.index) # NOMU
print(summary.is_delayed) # True/False by entitlement
API Reference
Base URL: https://api.sahmk.sa/api/v1
| Endpoint | Plan | Description | |----------|------|-------------| | GET /quote/{symbol}/ | Free | Stock quote | | GET /quotes/?symbols=... | Starter+ | Batch quotes (up to 50) | | GET /historical/{symbol}/ | Starter+ | Historical OHLCV data (1d, 1w, 1m, 30m, 60m, plan-limited) | | GET /market/summary/ | Free | Market overview | | GET /market/gainers/ | Free | Top gainers | | GET /market/losers/ | Free | Top losers | | GET /market/volume/ | Free | Volume leaders | | GET /market/value/ | Free | Value leaders | | GET /market/sectors/ | Free | Sector performance | | GET /market/depth/{symbol}/ | Entitled | Market depth / order book (levels 1-20). Request access | | GET /market/trades/{symbol}/ | Pro+ | Recent live trade prints (limit 1-200) | | GET /companies/ | Free | Company directory and symbol discovery | | GET /company/{symbol}/ | Free+ | Company info (tiered by plan) | | GET /financials/{symbol}/ | Starter+ | Financial statements | | GET /analytics/ratios/{symbol}/ | Starter+ | Analytics ratios for one company | | GET /analytics/compare/ | Starter+ | Analytics comparison across companies | | GET /dividends/{symbol}/ | Starter+ | Dividend history and yield | | GET /events/ | Pro+ | AI-generated stock events |
All endpoints require X-API-Key.
Full docs: sahmk.sa/developers/docs
Examples
Example scripts:
- quote.py
- batch_quotes.py
- historical.py
- market_summary.py
- depth.py
- trades.py
- analytics.py
- websocket_stream.py
- websocket_depth.py
- websocket_trades.py
Market Depth
Market depth REST and WebSocket access are entitlement-gated. Request access from the developer realtime-access dashboard.
depth = client.depth("2222", levels=5)
print(depth.bestbid, depth.bestask, depth.spread)
for level in depth.bids:
print(level.level, level.price, level.quantity)
CLI:
sahmk depth 2222 --levels 5
sahmk stream-depth 2222,1120 --levels 5
Live Trades (Pro+)
Recent trade prints (REST) and a real-time tape (WebSocket). Same Pro+ plan bar as Best Price–style realtime — no separate trades product request.
trades = client.trades("2222", limit=20)
print(trades.count, trades.summary.trade_value)
for event in trades.events:
print(event.event_time, event.price, event.quantity)
CLI:
sahmk trades 2222 --limit 20
sahmk stream-trades 2222,1120
WebSocket Streaming (Pro+)
import asyncio
from sahmk import SahmkClient
client = SahmkClient("yourapikey")
async def on_quote(msg): print(f"{msg['symbol']}: {msg['data']['price']}")
asyncio.run(client.stream(["2222", "1120"], onquote=onquote))
Depth streaming uses a dedicated channel:
async def on_depth(msg):
print(f"{msg['symbol']}: {msg['bestbid']} / {msg['bestask']}")
asyncio.run(client.streamdepth(["2222"], ondepth=on_depth, levels=5))
Trades streaming also uses a dedicated channel:
async def on_trade(msg):
print(f"{msg['symbol']}: {msg['price']} x {msg['quantity']}")
async def on_snapshot(msg): print(f"snapshot {msg['symbol']}: {msg['count']} events")
asyncio.run( client.streamtrades(["2222"], ontrade=ontrade, onsnapshot=on_snapshot) )
The streaming client auto-reconnects with exponential backoff + jitter and resubscribes symbols after reconnect.
Runtime behavior (verified with backend contract):
- Authentication close code:
4401(non-retryable) - Entitlement / plan / inactive / unverified close code:
4403(non-retryable) - Invalid JSON / unknown action returns
type="error"while socket stays open - Active subscriptions are per-connection; the SDK automatically resubscribes after reconnect
- Symbol chunking uses backend
connected.limits.maxsymbolsper_callwhen available
License
MIT — see LICENSE