Skip to content

API

NSE prices, history, fundamentals and news as JSON, plus webhooks that call your server when a price crosses a level.

Open the dashboard to register an app and get a consumer key and secret. Base URL: https://soko.dalasystems.com/api

1. Get a token

Exchange your consumer key and secret for a JWT access token (valid 1 hour) and a refresh token (valid 30 days). Send credentials as a form, JSON, or HTTP Basic.

curl -X POST https://soko.dalasystems.com/api/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$CONSUMER_KEY \
  -d client_secret=$CONSUMER_SECRET

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_..."
}

2. Refresh it

Refresh tokens rotate: every refresh returns a new one and the old one stops working. Reusing an old refresh token revokes all of the app’s refresh tokens, so store the newest one. Rotating the secret on the dashboard also revokes them.

curl -X POST https://soko.dalasystems.com/api/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=$REFRESH_TOKEN

3. Call the API

curl https://soko.dalasystems.com/api/v1/prices/SCOM -H "Authorization: Bearer $ACCESS_TOKEN"

{
  "data": { "ticker": "SCOM", "price": 33.3, "change": -0.8, "change_pct": -2.35, "volume": 1204500, "as_of": "2026-10-23T12:25:00Z" },
  "meta": { "as_of": "2026-10-23", "stale": false, "currency": "KES", "disclaimer": "..." }
}

Every response is { data, meta }. Check meta.as_of and meta.stale: NSE is thinly traded and prices can be old. Limit: 120 requests per minute per app (429 with Retry-After). Errors are { "detail": "..." }; token errors are 401.

EndpointReturns
GET /v1/pricesLatest price, change, volume and ranges for every counter
GET /v1/prices/{ticker}One counter
GET /v1/prices/{ticker}/history?start=&end=&limit=Daily closes, oldest first (limit up to 5000)
GET /v1/fundamentalsEPS, P/E, DPS, gross and net yield (after 5% WHT)
GET /v1/news?ticker=&kind=official|press&limit=Headlines and source links, tagged by ticker
GET /v1/dividendsUpcoming dividends with gross and net amounts
GET /v1/calendarUpcoming AGMs, results and rights issues
POST /webhooksRegister a price webhook
GET /webhooksList your webhooks
PATCH /webhooks/{id}Enable, disable or change a webhook
DELETE /webhooks/{id}Remove a webhook
POST /webhooks/{id}/testSend a signed sample event now
GET /webhooks/{id}/deliveriesRecent delivery attempts

Webhook routes are under the same base URL and accept the same bearer token (or your dashboard session).

Price webhooks

Register a URL, a ticker, a direction and a price. When the latest price reaches it, Soko POSTs a signed JSON event to your URL. Prices are checked after each scrape during trading hours (Mon to Fri, 09:00 to 15:00 EAT), so expect delivery within minutes, not seconds. If the price already meets the condition when you register, it fires on the next check.

curl -X POST https://soko.dalasystems.com/api/webhooks \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/soko-hook","ticker":"SCOM","direction":"ABOVE","threshold_price":35,"repeat":false}'

# the response includes "secret": "whsec_..." once. Store it.
POST /soko-hook
X-Soko-Event: price.alert
X-Soko-Delivery: evt_12_2026-10-26
X-Soko-Timestamp: 1790000000
X-Soko-Signature: sha256=9f2c...

{"id":"evt_12_2026-10-26","type":"price.alert","created_at":"2026-10-26T07:05:00+00:00","test":false,
 "data":{"ticker":"SCOM","condition":"above","threshold_price":35.0,"price":35.2,"currency":"KES","subscription_id":12}}
import hmac, hashlib

def valid(secret: str, timestamp: str, raw_body: bytes, header: str) -> bool:
    mac = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256)
    return hmac.compare_digest("sha256=" + mac.hexdigest(), header)

Prices are end-of-day closes or delayed prints from public sources. General information, not personalised financial advice.