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:
SAHMK_API_KEY). - 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["SAHMK_API_KEY"])
quote = client.quote("2222")
print(f"{quote['name_en']}: {quote['price']} SAR ({quote['change_percent']}%)")
market = client.market_summary(index="TASI")
print(f"TASI: {market['index_value']} ({market['index_change_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"
Symbol input always works. Name/alias input requires backend identifier-resolution support.
For batch quotes, the SDK first tries 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("your_api_key", 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
Out-of-plan interval/range requests are returned by the API as 403 PLAN_LIMIT.
The SDK does not hard-block these requests client-side; it passes the API response through.
CLI Quick Start
export SAHMK_API_KEY="your_api_key"
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 your_api_key
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
(income_statements, balance_sheets, 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, optional side) |
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.best_bid, depth.best_ask, 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, event.side or "-")
event.side is optional and may be "buy", "sell", or None depending on backend availability.
CLI:
sahmk trades 2222 --limit 20
sahmk stream-trades 2222,1120
WebSocket Streaming (Pro+)
import asyncio
from sahmk import SahmkClient
client = SahmkClient("your_api_key")
async def on_quote(msg):
print(f"{msg['symbol']}: {msg['data']['price']}")
asyncio.run(client.stream(["2222", "1120"], on_quote=on_quote))
Depth streaming uses a dedicated channel:
async def on_depth(msg):
print(f"{msg['symbol']}: {msg['best_bid']} / {msg['best_ask']}")
asyncio.run(client.stream_depth(["2222"], on_depth=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']} side={msg.get('side') or '-'}")
async def on_snapshot(msg):
print(f"snapshot {msg['symbol']}: {msg['count']} events")
asyncio.run(
client.stream_trades(["2222"], on_trade=on_trade, on_snapshot=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.max_symbols_per_callwhen available
For a production-style long-running process (logging, graceful shutdown, reconnect visibility, and automatic resubscribe behavior), see:
Changelog: CHANGELOG.md
Roadmap: ROADMAP.md
License
MIT — see LICENSE
Metadata
Release files for sahmk 0.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sahmk-0.16.0.tar.gz | 58.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sahmk-0.16.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.6 kB
Release files / sahmk-0.16.0.tar.gz
| Download URL | sahmk-0.16.0.tar.gz |
|---|---|
| Size | 58.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4b9db8b93361ad1d33d95e742842da35968ea530bd6d8f6e8a8819fd2582e8de
|
|
BLAKE2b-256 checksum How to use checksums |
44acae2320e6c489a45db7544a9d8b98bd997d5a78327a5d38254eae39797cb9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / sahmk-0.16.0-py3-none-any.whl
| Download URL | sahmk-0.16.0-py3-none-any.whl |
|---|---|
| Size | 27.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3c4c5f9838319e9e02cc8b31251a587b1cb0e2e1985367c1d99a5465815fea6f
|
|
BLAKE2b-256 checksum How to use checksums |
a2421836a18a3cc803649a705cc4ec24c4c8006ee379dae1af401095780028ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|