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_TOKEN3. 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.
| Endpoint | Returns |
|---|---|
| GET /v1/prices | Latest 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/fundamentals | EPS, 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/dividends | Upcoming dividends with gross and net amounts |
| GET /v1/calendar | Upcoming AGMs, results and rights issues |
| POST /webhooks | Register a price webhook |
| GET /webhooks | List your webhooks |
| PATCH /webhooks/{id} | Enable, disable or change a webhook |
| DELETE /webhooks/{id} | Remove a webhook |
| POST /webhooks/{id}/test | Send a signed sample event now |
| GET /webhooks/{id}/deliveries | Recent 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}}- Verify the signature. Compute
HMAC-SHA256(secret, timestamp + "." + rawBody)as hex, compare to the header in constant time, and reject timestamps more than 5 minutes old. - Reply with any 2xx within 5 seconds. Redirects are not followed.
- One-shot by default: it deactivates after one successful delivery. With
repeatit fires at most once per day while the condition holds. - Failures retry on the next price check. After 5 failures in a row the webhook is disabled; fix the endpoint and re-enable it on the dashboard.
- The event id is stable per webhook per day: dedupe on it.
- URLs must be https and resolve to public addresses. Up to 10 webhooks and 5 apps per account.
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.