Developers API

API Documentation

Welcome to the SAHMK API. Real-time and historical Saudi stock market data for all 350+ companies on TASI and Nomu — complete reference for endpoints, authentication, streaming, and tools.

Getting Started

Create an account, get your API key, and make your first request. Then move from core REST endpoints to SDKs, automation, and real-time streaming as your product grows.

Quick Start

  1. Create a free account
  2. Get your API key from the dashboard
  3. Make your first API request
  4. Browse code examples on GitHub

Base URL

text
https://app.sahmk.sa/api/v1

Your First Request

curl -X GET "https://app.sahmk.sa/api/v1/quote/2222/" \
     -H "X-API-Key: YOUR_API_KEY"

Full examples available on GitHub →

Quote path uses a symbol. If you need name/alias resolution, pass it through the optional identifier query param.

Expected Response

json
{
  "symbol": "2222",
  "name_en": "Saudi Arabian Oil Co",
  "price": 25.86,
  "change_percent": 0.7,
  "volume": 9803705,
  "updated_at": "2026-02-10T12:19:22+00:00",
  "is_delayed": false
}

Success: You just fetched live Saudi market data from SAHMK. Next, use the SDK for faster integration, explore core endpoints, or move to WebSocket if you need streaming updates.

Ambiguity note: If an identifier maps to multiple companies, pass the exact exchange symbol to disambiguate.

Authentication

All API requests require authentication using an API key. Include your key in the X-API-Key header.

http
X-API-Key: YOUR_API_KEY

API Key Types:
shmk_live_* — Production keys
shmk_test_* — Test keys (same data, separate quota)

curl -X GET "https://app.sahmk.sa/api/v1/quote/2222/" \
     -H "X-API-Key: YOUR_API_KEY"

Python SDK & CLI

Start here if you want the fastest path from API key to working integration. The official SDK gives you a cleaner client, while the CLI is useful for testing, demos, and automated tooling.

bash
pip install -U sahmk
export SAHMK_API_KEY="your_api_key"
sahmk quote "Saudi Aramco"

Python SDK:

python
from sahmk import SahmkClient

client = SahmkClient(api_key="YOUR_API_KEY")

directory = client.companies(search="aramco", market="TASI", limit=20, offset=0)
symbol = directory["results"][0]["symbol"]

print(client.quote("أرامكو السعودية"))

If symbol input is uncertain, call client.companies(...) first to discover a valid symbol before quote/company calls.

Use this when: you want less boilerplate than raw REST, typed workflows in Python, or quick command-line access to quotes and market data.

Quote methods accept identifiers (symbol, Arabic/English name, alias). Use symbol when the identifier is ambiguous.

Full examples: github.com/sahmk-sa/sahmk-pythonPyPI: pypi.org/project/sahmk

AI & Agents

Use SAHMK inside Claude Desktop, Cursor, and other MCP-compatible clients. For direct agent consumption, use the MCP server for tool calling and/api-docs.mdfor machine-readable API docs.

bash
pip install sahmk-mcp

Package: pypi.org/project/sahmk-mcp · MCP quick start tutorial

Claude Desktop

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json

json
{
  "mcpServers": {
    "sahmk": {
      "command": "sahmk-mcp",
      "env": {
        "SAHMK_API_KEY": "your_api_key_here"
      }
    }
  }
}

Cursor

Add to your project's .cursor/mcp.json

json
{
  "mcpServers": {
    "sahmk": {
      "command": "sahmk-mcp",
      "env": {
        "SAHMK_API_KEY": "your_api_key_here"
      }
    }
  }
}

Use this when: your team wants SAHMK available inside AI workflows, research assistants, or agentic tools without building extra integration glue first.

Use companies_list for symbol discovery (`search`, `market`, `limit`, `offset`) before quote tools.

Stocks

Get real-time stock quotes and prices for individual or multiple companies.

GET /quote/{symbol}/

Free

Get current price and trading data for a single stock symbol.

Path Parameters:

  • symbol — Exchange symbol (e.g., 2222)

Optional Query Parameters:

  • identifier — Arabic/English name or alias for resolution (example: أرامكو)
http
GET /api/v1/quote/2222/?identifier=أرامكو

Need symbols? Use GET /companies/

View Response
json
{
  "symbol": "2222",
  "name": "أرامكو السعودية",
  "name_en": "Saudi Arabian Oil Co",
  "price": 25.86,
  "change": 0.18,
  "change_percent": 0.7,
  "open": 25.6,
  "high": 25.86,
  "low": 25.6,
  "previous_close": 25.68,
  "volume": 9803705,
  "value": 252308343.0,
  "bid": 25.82,
  "ask": 25.86,
  "liquidity": {
    "inflow_value": 184950463.03,
    "inflow_volume": 7182468,
    "inflow_trades": 7261,
    "outflow_value": 67357881.91,
    "outflow_volume": 2621237,
    "outflow_trades": 5028,
    "net_value": 117592581.12
  },
  "updated_at": "2026-02-10T12:19:22+00:00",
  "is_delayed": false
}

Liquidity Fields:

  • inflow_value — Total SAR value of buy orders
  • outflow_value — Total SAR value of sell orders
  • net_value — Net liquidity (inflow - outflow)

GET /quotes/

Starter+

Get quotes for multiple companies in a single request. Requires Starter plan or higher.

Note: This bulk endpoint requires a Starter plan or higher. Free-tier users can use GET /quote/{symbol}/ for individual quotes.

Query Parameters:

  • symbols — Comma-separated exchange symbols (max 50)
  • identifiers — Comma-separated Arabic/English names or aliases

Use symbols or identifiers, not both in the same request.

Need symbols? Use GET /companies/

Pro Tip: Use this batch endpoint instead of polling individual /quote/{symbol}/ calls — it's more efficient and counts as a single API request.

View Response
json
{
  "quotes": [
    {
      "symbol": "2222",
      "name": "أرامكو السعودية",
      "name_en": "Saudi Arabian Oil Co",
      "price": 25.86,
      "change": 0.18,
      "change_percent": 0.7,
      "volume": 9803705,
      "net_liquidity": 117592581.12,
      "updated_at": "2026-02-10T12:19:22+00:00",
      "is_delayed": false
    },
    {
      "symbol": "1120",
      "name": "الراجحي",
      "name_en": "Al Rajhi Banking & Investment Corp SJSC",
      "price": 108.6,
      "change": 0.2,
      "change_percent": 0.18,
      "volume": 4023570,
      "net_liquidity": 45230000.50,
      "updated_at": "2026-02-10T12:18:56+00:00",
      "is_delayed": false
    }
  ],
  "count": 2
}

net_liquidity — Net money flow (buy value - sell value) in SAR

Path vs query parameters: Use path parameters (for example /quote/{symbol}/) for a single resource, and query parameters (for example /events/?symbol=2222&limit=20) to filter list endpoints.

Error handling convention: Common HTTP/API errors are centralized in Error Codes. Endpoint sections only show endpoint-specific errors when needed.

Market

Get market-wide data including index values, top movers, and sector performance.

Market index query parameter:

  • index is optional on all market endpoints
  • Supported values: TASI, NOMU
  • Default when omitted: TASI
  • Existing URLs without index still work

GET /market/summary/?index=TASI

Free

Get market index value, volume, and market sentiment.

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "timestamp": "2026-01-28T12:20:00+00:00",
  "index_value": 11458.11,
  "index_change": 76.28,
  "index_change_percent": 0.67,
  "total_volume": 279874553,
  "advancing": 117,
  "declining": 139,
  "unchanged": 14,
  "market_mood": "Bullish"
}

GET /market/gainers/?limit=10&index=TASI

Free

Get top gaining stocks by percentage change.

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
  • limit — Number of results (default: 10, max: 50)
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "gainers": [
    {
      "symbol": "4194",
      "name": "مجموعة منزل التسويق للتجارة",
      "name_en": "Maison Marketing Trade Group",
      "price": 59.5,
      "change": 4.9,
      "change_percent": 8.97,
      "volume": 611349,
      "updated_at": "2026-01-28T12:19:50+00:00"
    }
  ],
  "count": 10
}

GET /market/losers/?limit=10&index=TASI

Free

Get top losing stocks by percentage change.

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
  • limit — Number of results (default: 10, max: 50)
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "losers": [
    {
      "symbol": "9639",
      "name": "شركة أنماط التقنية للتجارة",
      "name_en": "Anmat Technology Trading Co",
      "price": 8.2,
      "change": -0.8,
      "change_percent": -8.89,
      "volume": 9206,
      "updated_at": "2026-01-28T12:10:18+00:00"
    }
  ],
  "count": 10
}

GET /market/volume/?limit=10&index=TASI

Free

Get top stocks by trading volume.

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
  • limit — Number of results (default: 10, max: 50)
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "stocks": [
    {
      "symbol": "2222",
      "name": "أرامكو السعودية",
      "name_en": "Saudi Arabian Oil Co",
      "price": 25.64,
      "change": 0.38,
      "change_percent": 1.5,
      "volume": 15738067,
      "updated_at": "2026-01-28T12:19:48+00:00"
    }
  ],
  "count": 10
}

GET /market/value/?limit=10&index=TASI

Free

Get top stocks by trading value (SAR).

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
  • limit — Number of results (default: 10, max: 50)
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "stocks": [
    {
      "symbol": "2222",
      "name": "أرامكو السعودية",
      "name_en": "Saudi Arabian Oil Co",
      "price": 25.64,
      "change": 0.38,
      "change_percent": 1.5,
      "volume": 15738067,
      "value": 402108076.72,
      "updated_at": "2026-01-28T12:19:48+00:00"
    }
  ],
  "count": 10
}

GET /market/sectors/?index=TASI

Free

Get sector performance and statistics.

Query Parameters:

  • index — Market index (TASI or NOMU). Default: TASI
View Response
json
{
  "index": "TASI",
  "is_delayed": true,
  "sectors": [
    {
      "id": "TBNI",
      "name": "Banks",
      "change_percent": 0.45,
      "avg_change_percent": 0.38,
      "volume": 45027873,
      "num_stocks": 10
    }
  ],
  "count": 20
}
View Endpoint-Specific Error (invalid index)
json
{
  "error": {
    "code": "INVALID_INDEX",
    "message": "Invalid index 'XYZ'. Supported values: TASI, NOMU."
  }
}

Company / Symbol APIs

Discover valid symbols and fetch detailed company information.

GET /companies/

Free

Lightweight company directory for symbol discovery before calling quote or company endpoints.

Query Parameters:

  • search — Matches symbol, Arabic name, or English name
  • marketTASI or NOMU (NOMUC alias accepted)
  • limit — Default: 100, max: 500
  • offset — Default: 0
View Response
json
{
  "results": [
    {
      "symbol": "2222",
      "name_ar": "أرامكو السعودية",
      "name_en": "Saudi Arabian Oil Co",
      "market": "TASI",
      "status": "active"
    }
  ],
  "count": 1,
  "total": 590,
  "limit": 100,
  "offset": 0
}
View Errors
json
{
  "error": {
    "code": "INVALID_MARKET",
    "message": "market must be one of: TASI, NOMU."
  }
}

{
  "error": {
    "code": "INVALID_PARAM",
    "message": "limit and offset must be valid integers."
  }
}

GET /company/{symbol}/

Free

Get company information. Response varies by plan.

Data Available by Plan:

  • Free: Name, sector, industry, description, website
  • Starter: + Full fundamentals (PE, EPS, book value, beta, week/month/52w ranges)
  • Pro+: + Technicals, valuation, analyst consensus

is_delayed indicates pricing freshness: true for delayed prices (Free/Starter) and false for real-time prices (Pro/Business/Enterprise).

New: week_high/low, month_high/low — Price levels in last 7/30 days

View Response (Pro+)
json
{
  "symbol": "2222",
  "name": "أرامكو السعودية",
  "name_en": "Saudi Arabian Oil Co",
  "current_price": 25.64,
  "is_delayed": false,
  "sector": "Energy",
  "industry": "Oil & Gas",
  "description": "Saudi Aramco is the world's largest oil producer...",
  "website": "https://www.aramco.com",
  "country": "Saudi Arabia",
  "currency": "SAR",
  
  "fundamentals": {
    "market_cap": 6258120000000,
    "pe_ratio": 16.77,
    "forward_pe": 15.48,
    "eps": 1.54,
    "book_value": 6.16,
    "price_to_book": 4.19,
    "beta": 0.104,
    "shares_outstanding": 242000000000,
    "float_shares": 5969578000,
    "week_high": 26.10,
    "week_low": 25.40,
    "month_high": 27.20,
    "month_low": 24.80,
    "fifty_two_week_high": 27.85,
    "fifty_two_week_low": 23.04
  },
  
  "technicals": {
    "rsi_14": 55.3,
    "macd_line": 0.12,
    "macd_signal": 0.08,
    "macd_histogram": 0.04,
    "fifty_day_average": 26.1,
    "technical_strength": 0.65,
    "price_direction": "bullish",
    "updated_at": "2026-01-28T10:00:00+03:00"
  },
  
  "valuation": {
    "fair_price": 28.50,
    "fair_price_confidence": 0.85,
    "calculated_at": "2026-01-28T10:00:00+03:00"
  },
  
  "analysts": {
    "target_mean": 29.5,
    "target_median": 29.0,
    "target_high": 35.0,
    "target_low": 24.0,
    "consensus": "buy",
    "consensus_score": 2.1,
    "num_analysts": 15
  }
}

Historical Data

Access historical OHLCV (Open, High, Low, Close, Volume) data for technical analysis and backtesting.

GET /historical/{symbol}/

Starter+

Get historical price data for a stock.

Query Parameters:

  • from — Start date YYYY-MM-DD (default: 30 days ago)
  • to — End date YYYY-MM-DD (default: today)
  • interval — Accepted values: 1d, 1w, 1m (default: 1d; 1w and 1m are aggregated from daily candles)
View Response
json
{
  "symbol": "2222",
  "interval": "1d",
  "from": "2026-01-01",
  "to": "2026-01-28",
  "count": 20,
  "data": [
    {
      "date": "2026-01-28",
      "open": 25.3,
      "high": 25.68,
      "low": 25.3,
      "close": 25.64,
      "volume": 15738067,
      "adjusted_close": 25.64,
      "turnover": 402108076.72
    }
  ]
}

Financials

Access structured income statement, balance sheet, and cash flow data for Saudi listed companies.

GET /financials/{symbol}/

Starter+
View Request Examples
bash
# Starter
GET /api/v1/financials/1120/

# Pro/Business/Enterprise
GET /api/v1/financials/1120/?period=quarterly&history=5y&metrics=extended

Key Parameters

  • typeincome, balance, cashflow, all
  • periodannual, quarterly
  • history1y, 3y, 5y, 10y, max
  • metricscore, extended
  • resultseries, latest
  • include_quality=1 — Include metadata and coverage info
  • include_future_placeholders=1 — Include future-dated placeholder rows (default is hidden)

Data Available by Plan:

  • Starter: Annual + core + up to 3y + latest snapshot
  • Pro+: Pro, Business, and Enterprise include quarterly + extended + 5Y/10Y/max + full views

Notes

Response format: Financial statements are returned directly in statement arrays such as income_statements, balance_sheets, and cash_flows.

Included fields: You also get fields such as symbol, statement_period, and optional quality when requested.

Response Example

View Response
json
{
  "symbol": "1120",
  "statement_period": "annual",
  "quality": {
    "coverage": "high",
    "warnings": []
  },
  "income_statements": [
    {
      "report_date": "2025-09-30",
      "total_revenue": 123456789.0,
      "net_income": 9876543.0
    }
  ]
}

Analytics

Compare and analyze company ratios with compact analytics endpoints.

Response meta: Analytics responses include compact meta fields: period, metrics, warnings.

Ratio keys: Available ratio keys vary by symbol and data coverage, so render keys dynamically instead of assuming a fixed schema.

GET /analytics/ratios/{symbol}/

Starter+

Financial ratios for one symbol.

Query Parameters:

  • historylatest, 3y, 5y, 10y, max (default: latest)
  • periodannual, quarterly (default: annual)
  • metricscore, extended (default: core)

Note: Free has no analytics access. Starter supports latest + annual + core only. Pro, Business, and Enterprise unlock all ratios options.

Pro Tip: Start with history=latest for low-latency cards, then load longer history only when users open detail views.

View Request Examples
bash
# Starter
GET /api/v1/analytics/ratios/1120/

# Pro/Business/Enterprise
GET /api/v1/analytics/ratios/1120/?history=5y&period=quarterly&metrics=extended
View Ratios Response
json
{
  "symbol": "1120",
  "ratios": [
    {
      "report_date": "2025-12-31",
      "statement_period": "annual",
      "fiscal_year": 2025,
      "fiscal_quarter": null,
      "ratios": {
        "roe": 9.77,
        "roa": 3.89,
        "net_margin": 19.09,
        "debt_to_equity": 0.6,
        "revenue_growth_yoy": 10.0,
        "net_income_growth_yoy": 16.67
      },
      "key_metrics": {
        "total_revenue": 1100.0,
        "operating_income": 250.0,
        "net_income": 210.0,
        "operating_cash_flow": 280.0,
        "total_assets": 5400.0,
        "stockholders_equity": 2150.0,
        "total_debt": 1300.0
      }
    }
  ],
  "meta": {
    "period": "annual",
    "metrics": "core",
    "warnings": []
  }
}

GET /analytics/compare/

Starter+

Compare ratio snapshots across multiple symbols.

Query Parameters:

  • symbols — required comma-separated symbols (example: 1120,1180,1010)
  • metricscore, extended (default: core)

Note: Starter supports up to 3 symbols and core metrics only. Pro supports up to 10 symbols, while Business and Enterprise support up to 20 symbols; all three support extended.

Pro Tip: Keep a small default symbol set for fast first paint, then let users add symbols progressively.

View Request Examples
bash
# Starter
GET /api/v1/analytics/compare/?symbols=1120,1180,1010

# Business/Enterprise
GET /api/v1/analytics/compare/?symbols=1120,1180,1010,2222&metrics=extended
View Compare Response
json
{
  "results": [
    {
      "symbol": "1120",
      "company_name": "الراجحي",
      "sector": "Financial Services",
      "market_cap": 1000001120.0,
      "current_price": 89.4,
      "coverage": "high",
      "ratios": {
        "roe": 9.77,
        "roa": 3.89,
        "net_margin": 19.09,
        "debt_to_equity": 0.6,
        "revenue_growth_yoy": 10.0,
        "net_income_growth_yoy": 16.67,
        "asset_turnover": 0.2037,
        "debt_ratio": 0.2407
      },
      "key_metrics": {
        "total_revenue": 1100.0,
        "operating_income": 250.0,
        "net_income": 210.0,
        "operating_cash_flow": 280.0,
        "total_assets": 5400.0,
        "stockholders_equity": 2150.0,
        "total_debt": 1300.0
      }
    }
  ],
  "count": 1,
  "meta": {
    "period": "annual",
    "metrics": "extended",
    "warnings": []
  }
}

Dividends

Get dividend history, upcoming distributions, and trailing yield for a stock.

GET /dividends/{symbol}/

Starter+

Get dividend history and yield information.

Query Parameters:

  • limit — Number of records (default: 10, max: 50)
View Response
json
{
  "symbol": "2222",
  "current_price": 25.64,
  "trailing_12m_yield": 4.2,
  "trailing_12m_dividends": 1.60,
  "payments_last_year": 4,
  "upcoming": [
    {
      "value": 0.40,
      "period": "Q4",
      "eligibility_date": "2026-03-15",
      "distribution_date": "2026-04-01"
    }
  ],
  "history": [
    {
      "value": 0.40,
      "value_percent": 1.5,
      "period": "Q3",
      "fiscal_year": 2025,
      "announcement_date": "2025-09-01",
      "eligibility_date": "2025-09-15",
      "distribution_date": "2025-10-01"
    }
  ]
}

Stock Events

Get AI-generated summaries of significant stock events and news.

GET /events/

Pro+

Get stock events with AI-generated analysis.

Query Parameters:

  • symbol — Filter by stock ticker (optional)
  • type — Filter by event type (optional, UPPERCASE)
  • importance — Filter by importance: CRITICAL, IMPORTANT, REGULAR (optional)
  • limit — Number of results (default: 20, max: 100)

Note: Event types are UPPERCASE (e.g., FINANCIAL_REPORT, DIVIDEND_ANNOUNCEMENT).

View Response
json
{
  "events": [
    {
      "symbol": "4190",
      "stock_name": "جرير للتسويق",
      "event_type": "FINANCIAL_REPORT",
      "importance": "important",
      "sentiment": "positive",
      "description": "شركة جرير للتسويق تعلن عن نتائج مالية قياسية للربع الرابع 2025...",
      "event_date": "2026-01-29",
      "article_date": "2026-01-29T17:10:06+00:00",
      "created_at": "2026-01-29T17:10:12+00:00"
    }
  ],
  "count": 1,
  "available_types": [
    "FINANCIAL_REPORT", "DIVIDEND_ANNOUNCEMENT", "STOCK_SPLIT",
    "MERGER_ACQUISITION", "MANAGEMENT_CHANGE", "NEW_LISTING",
    "REGULATORY_ACTION", "PARTNERSHIP", "MARKET_EXPANSION",
    "RESTRUCTURING", "EARNINGS_SURPRISE", "OTHER"
  ]
}

available_types: Server-defined list that may expand in future releases.

importance: critical, important, regular

sentiment: very_positive, positive, slightly_positive, neutral, slightly_negative, negative, very_negative

WebSocket Streaming

Real-time stock price streaming via WebSocket, with market updates pushed as soon as they change.

Connection

Pro+

WebSocket URL:

text
wss://app.sahmk.sa/ws/v1/stocks/?api_key=YOUR_API_KEY

Pro/Business/Enterprise: WebSocket streaming requires Pro, Business, or Enterprise plan. Updates are delivered during active trading sessions. Enterprise limits may vary by delivery profile.

Subscription Limits

PlanMax symbols/connectionMax symbols/callSubscribe all (*)
Pro6020
Business12040
Enterprise20020 (or 100 on enterprise realtime profile)

Notes: Use multiple connections to exceed your plan's per-connection cap. Limits are also returned in the initial connected message under limits.

Client → Server Messages

ActionMessageDescription
Subscribe{"action": "subscribe", "symbols": ["2222", "1120"]}Subscribe to specific stocks
Subscribe All{"action": "subscribe", "symbols": ["*"]}Subscribe to all stocks (Enterprise only)
Unsubscribe{"action": "unsubscribe", "symbols": ["2222"]}Stop receiving updates for symbols
Ping{"action": "ping"}Keep-alive

Server → Client Messages

TypeDescription
connectedConnection confirmed with plan and limits
subscribedSubscription confirmed
unsubscribedUnsubscribe confirmed
quoteReal-time price update
pongPing response
errorError message
View Connected Message Format
json
{
  "type": "connected",
  "plan": "business",
  "delivery_profile": "business_standard",
  "limits": {
    "max_symbols_per_connection": 120,
    "max_symbols_per_call": 40,
    "stream_modes": ["standard"]
  },
  "message": "Connected to SAHMK real-time stock stream",
  "timestamp": "2026-02-10T10:00:00.000Z"
}
View Quote Message Format
json
{
  "type": "quote",
  "symbol": "2222",
  "mode": "standard",
  "timestamp": "2026-02-10T10:30:15.123Z",
  "latency_ms": 42,
  "data": {
    "price": 25.86,
    "open": 25.60,
    "high": 25.86,
    "low": 25.60,
    "close": 25.86,
    "change": 0.18,
    "change_percent": 0.7,
    "previous_close": 25.68,
    "volume": 9803705,
    "value": 252308343.0,
    "bid": 25.82,
    "ask": 25.86,
    "market_session": "REGULAR",
    "liquidity": {
      "inflow_value": 184950463.03,
      "inflow_volume": 7182468,
      "outflow_value": 67357881.91,
      "outflow_volume": 2621237,
      "net_value": 117592581.12
    },
    "trade_time": "2026-02-10T10:30:12+00:00"
  }
}

Code Examples

const API_KEY = "shmk_live_xxxxxxxxxxxxxxxx";
const ws = new WebSocket(`wss://app.sahmk.sa/ws/v1/stocks/?api_key=${API_KEY}`);

ws.onopen = () => {
  ws.send(JSON.stringify({
    action: "subscribe",
    symbols: ["2222", "1120", "4191"]
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "connected") {
    console.log("Plan:", msg.plan, "Limits:", msg.limits);
  }

  if (msg.type === "quote") {
    console.log(`${msg.symbol}: ${msg.data.price} (${msg.data.change_percent}%)`);
  }
};

// Keep-alive ping every 30 seconds
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: "ping" }));
  }
}, 30000);

View more examples on GitHub →

REST vs WebSocket Comparison

MetricREST PollingWebSocket
Latency1-60+ seconds<1 second
API calls/dayThousands1 connection
Missed updatesPossibleNone
ComplexitySimpleSlightly more

WebSocket Error Codes

These are WebSocket service-specific errors. For common HTTP/API errors, see Error Codes.

CodeMeaning
4000Generic connection or server failure
4001Invalid or missing API key

Event Webhooks Pro+

Register webhook endpoints for Realtime Event Engine delivery. This extends existing alert callbacks with delivery status tracking and retry behavior for unsuccessful deliveries.

POST /api/v1/webhooks/

Create a webhook destination for Realtime Event Engine notifications.

Parameters:

  • url (required) — HTTPS destination URL
  • name (optional) — Friendly label for dashboard/use cases
curl -X POST "https://app.sahmk.sa/api/v1/webhooks/" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/sahmk",
    "name": "Trading Production Hook"
  }'

Practical rule examples

Use these templates directly with your own symbol, threshold, and webhook id.

# 1) Large move rule (>= 3% absolute move in 15m)
curl -X POST "https://app.sahmk.sa/api/v1/alerts/" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "2222",
    "event_type": "large_move",
    "condition": "pct_change_abs_gte",
    "value": 3.0,
    "webhook_id": 19,
    "once": false,
    "config": { "window": "15m" }
  }'

# 2) Abnormal volume rule (>= 2.5x baseline volume)
curl -X POST "https://app.sahmk.sa/api/v1/alerts/" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "2010",
    "event_type": "abnormal_volume",
    "condition": "ratio_gte",
    "value": 2.5,
    "webhook_id": 19,
    "once": false
  }'

# 3) Unusual traded value rule (>= 3.0x traded value baseline)
curl -X POST "https://app.sahmk.sa/api/v1/alerts/" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "1180",
    "event_type": "unusual_value_traded",
    "condition": "ratio_gte",
    "value": 3.0,
    "webhook_id": 19,
    "once": false
  }'
View Response
json
{
  "id": 19,
  "url": "https://example.com/hooks/sahmk",
  "name": "Trading Production Hook",
  "signing_secret": "xxxxxxxxxxxxxxxx",
  "is_verified": false,
  "is_active": true,
  "created_at": "2026-05-08T13:30:12+03:00"
}

Webhook signing: each delivery includes X-SAHMK-Signature (HMAC-SHA256 over canonical payload). Always verify signature and reject mismatches with HTTP 401.

Delivery lifecycle: pending retrying delivered or dead_letter. Retry backoff: 2s, 10s, 60s.

Event Rules Pro+

Event rules are the evolution of legacy price alerts. Create rules that evaluate market conditions and trigger webhook events for your subscribed symbols.

Supported event types:

price_alertlarge_moveabnormal_volumeunusual_value_traded

POST /api/v1/alerts/

Create a realtime event rule.

Parameters:

  • symbol (required) — Stock symbol such as 2222
  • event_type (required) — one of the realtime event types
  • condition (required in practice; type-specific) — condition selector used by rule matching
  • value (required) — numeric threshold
  • webhook_id (required) — destination created via webhooks endpoint
  • once (optional) — fire once then deactivate
  • config (optional) — deterministic options (for example window for large_move)

Condition rules:

  • price_alert: price_above, price_below, pct_change
  • large_move: pct_change_abs_gte (normalized by API)
  • abnormal_volume: ratio_gte
  • unusual_value_traded: ratio_gte
curl -X POST "https://app.sahmk.sa/api/v1/alerts/" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "2222",
    "event_type": "large_move",
    "condition": "pct_change_abs_gte",
    "value": 3.0,
    "webhook_id": 19,
    "once": false,
    "config": { "window": "15m" }
  }'
View Response
json
{
  "id": 42,
  "symbol": "2222",
  "event_type": "large_move",
  "condition": "pct_change_abs_gte",
  "value": "3.0",
  "webhook_id": 19,
  "status": "active",
  "fire_once": false
}

GET /api/v1/alerts/

List existing rules and filter by status.

Query options:

  • status=active|paused|all
curl "https://app.sahmk.sa/api/v1/alerts/?status=active" \
  -H "X-API-Key: YOUR_API_KEY"
View Response
json
{
  "alerts": [
    {
      "id": 42,
      "symbol": "2222",
      "event_type": "large_move",
      "condition": "pct_change_abs_gte",
      "value": "3.0",
      "fire_once": false,
      "status": "active",
      "webhook_id": 19,
      "created_at": "2026-05-08T13:36:45+03:00"
    }
  ]
}

Event History Pro+

Query recent realtime event deliveries and outcomes for observability, replay, and debugging.

GET /api/developers/events/

Return delivery history across event types and webhook endpoints.

Authentication: Developer JWT (Authorization: Bearer <token>)

Query options:

  • status — pending, delivered, retrying, dead_letter
  • event_type (optional) — filter by event type
  • limit, offset — pagination
curl "https://app.sahmk.sa/api/developers/events/?status=dead_letter&limit=20" \
  -H "Authorization: Bearer YOUR_DEVELOPER_JWT"
View Response
json
{
  "events": [
    {
      "event_id": "be03f577-c9d0-4d8f-a7ef-5353f11e59a7",
      "event_type": "abnormal_volume",
      "symbol": "2222",
      "detected_at": "2026-05-08T13:48:22+03:00",
      "delivery_status": "retrying",
      "delivery_attempts": 2,
      "last_attempt_number": 2,
      "webhook_id": 19
    }
  ],
  "total": 1,
  "has_more": false
}

Realtime Event Engine Error Codes

These are Realtime Event Engine service-specific errors. For common HTTP/API errors, see Error Codes.

HTTPError CodeMeaning
403PLAN_LIMITFree/Starter attempted Realtime Event Engine endpoints, or account event limits were reached.
400VALIDATIONInvalid payload field or unsupported query option.
404NOT_FOUNDWebhook, rule, or history record does not belong to this developer.
401INVALID_SIGNATUREWebhook receiver rejected signature verification.

Rate Limits

API access is rate-limited based on your subscription plan. There are two types of limits: daily quotas and per-minute burst limits.

Full Plan Comparison

PlanDaily LimitBurst LimitAPI KeysWebSocketEvent WebhooksEvent Rules
Free100/day10/min100
Starter5,000/day100/min300
Pro50,000/day500/min10310
Business150,000/day1,000/min301050
EnterpriseCustomCustomCustomCustomCustom

Burst Protection: To prevent abuse and protect stability, per-minute throttling is applied at both API-key and account levels. Requests exceeding these limits return HTTP 429. Daily limits reset at midnight (UTC+3). Enterprise limits are contract-based and may be monthly quotas or resource-based.

Rate Limit Headers

Each response includes headers to help track your usage:

http
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4987
X-RateLimit-Reset: 1769816400

X-RateLimit-Reset is a Unix timestamp (seconds).

Error Codes

The API uses standard HTTP status codes and returns structured JSON error responses.

400INVALID_ROUTE

Wrong endpoint path was used. The API returns a suggested correct route.

401INVALID_API_KEY

API key is missing, invalid, or revoked.

403PLAN_LIMIT

Endpoint requires a higher plan (e.g., historical data requires Starter+, or higher-tier financials combinations are requested on Starter).

404INVALID_SYMBOL

Company identifier not resolved in TASI or Nomu. Use the exchange symbol when ambiguous.

429RATE_LIMIT

Daily quota or per-minute burst limit exceeded.

500SERVER_ERROR

Internal server error. Please retry or contact support.

Some new free accounts may temporarily hit a security limit. If this happens, the API may return HTTP 429 with error code TEMP_SECURITY_LIMIT. User action: try again later or upgrade for higher limits.

Common integration error: Calling GET /api/v1/quote/batch/ returns 400 INVALID_ROUTE with route guidance.

json
{
  "error": {
    "code": "INVALID_ROUTE",
    "message": "Did you mean /api/v1/quotes/?symbols=2222,1120 ?"
  }
}

Error Response Format

json
{
  "error": {
    "code": "RATE_LIMIT",
    "message": "Daily request limit exceeded. Resets at midnight UTC+3."
  }
}

Market Data Usage

SAHMK market data may be used within your applications, tools, or products.

Developer plans are intended for development, internal tools, and small-scale applications.

Large-scale public market data platforms, commercial display services, or data redistribution may require an enterprise agreement.

Reselling market data or providing it as a standalone API or data feed is not permitted without a separate agreement with SAHMK.

Need More Help?

Check the machine-readable docs at /api-docs.md, browse examples on GitHub, or contact our team.