English | العربية | Bahasa Indonesia | Melayu | اردو | Français
live badge from the API, embed one for any symbol
Official Python client for the Halal Terminal API — Shariah stock screening across 5 audited methodologies (AAOIFI, DJIM, FTSE, MSCI, S&P), real-time market data, ETF look-through analysis, zakat & purification calculators.
pip install halalterminalPython ≥ 3.9. Single runtime dependency: requests.
Generate a free API key — no credit card required:
curl -s -X POST https://api.halalterminal.com/api/keys/generate \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
# {"api_key": "ht_…", "tier": "free", "tokens_per_month": 500}Pass the key to the client or set the HALAL_TERMINAL_API_KEY environment variable:
from halalterminal import Client
# Explicit key
ht = Client(api_key="ht_…")
# Env var fallback — no argument needed
# export HALAL_TERMINAL_API_KEY=ht_…
ht = Client()A handful of endpoints (get_disclaimers, health) are public and work without any key.
from halalterminal import Client
ht = Client(api_key="ht_…")
aapl = ht.screen("AAPL")
print(aapl.is_compliant) # True
print(aapl.compliance_explanation) # "Compliant under all 5 methodologies."
print(aapl.purification_rate) # 0.009 (0.9 % of dividends to purify)
# Every response carries inline compliance copy — render it in your UI
for d in aapl.disclaimers:
print(f"[{d.severity}] {d.text}")from halalterminal import Client
ht = Client(api_key="ht_…")
result = ht.screen("AAPL")
# Top-level result
print(result.symbol) # "AAPL"
print(result.is_compliant) # True / False / None
print(result.shariah_compliance_status) # "compliant" | "non_compliant" | "questionable"
print(result.business_screen_pass) # True
print(result.financial_screen_pass) # True
print(result.purification_rate) # 0.009 — share of dividends to purify
# Per-methodology breakdown (AAOIFI, DJIM, FTSE, MSCI, SP500S)
for methodology, details in result.by_methodology.items():
print(methodology, details["is_compliant"], details.get("reason"))
# Force a fresh API-side recalculation (bypasses the server cache)
fresh = ht.screen("NVDA", force_refresh=True)
# Anything not yet a typed attribute is in .raw
print(result.raw["some_new_field"])from halalterminal import Client
ht = Client(api_key="ht_…")
portfolio = ht.scan_portfolio(["AAPL", "MSFT", "JNJ", "BAC"])
print(portfolio.total) # 4
print(portfolio.compliant_count) # 3
print(portfolio.non_compliant_count)# 1
# Per-symbol detail lives in .raw["results"]
for item in portfolio.raw["results"]:
print(item["symbol"], item["is_compliant"])Symbols are uppercased automatically — "aapl" and "AAPL" are equivalent.
from halalterminal import Client
ht = Client(api_key="ht_…")
q = ht.get_quote("MSFT")
print(q.symbol) # "MSFT"
print(q.name) # "Microsoft Corporation"
print(q.price) # 421.76
print(q.change) # 2.34
print(q.change_percent) # 0.56
print(q.volume) # 19384721
print(q.market_cap) # 3134000000000from halalterminal import Client
ht = Client(api_key="ht_…")
zakat = ht.calculate_zakat(
holdings=[
{"symbol": "AAPL", "market_value": 25_000},
{"symbol": "MSFT", "market_value": 10_000},
],
gold_price_per_gram=65.0, # optional; API uses its own price if omitted
)
print(zakat.total_market_value) # 35000.0
print(zakat.nisab_threshold) # 5525.0
print(zakat.is_above_nisab) # True
print(zakat.total_zakat) # 875.0
# Per-holding zakat breakdown is in .raw["holdings"]
for h in zakat.raw["holdings"]:
print(h["symbol"], h.get("zakat_amount"))from halalterminal import (
Client,
ApiKeyError,
QuotaExceededError,
RateLimitError,
NotFoundError,
ServerError,
HalalTerminalError,
)
ht = Client(api_key="ht_…")
try:
result = ht.screen("AAPL")
except ApiKeyError:
# 401/403 — key missing, invalid, or deactivated
print("Check your API key at api.halalterminal.com/dashboard")
except QuotaExceededError as e:
# 429 with code=QUOTA_EXCEEDED — monthly token allowance exhausted
# e.detail carries the upgrade hint from the API
print(f"Quota exhausted. Hint: {e.detail}")
except RateLimitError:
# 429 from the upstream rate limiter (distinct from quota)
import time; time.sleep(2)
except NotFoundError:
# 404 — symbol not found in our database
print("Unknown ticker")
except ServerError:
# 5xx — server-side error, safe to retry with exponential backoff
raise
except HalalTerminalError as e:
# Catch-all for any other non-2xx response
print(e.status_code, e.code, str(e))Every exception carries .status_code, .code (the API's machine-readable error code), and .detail.
Every compliance, market-data, zakat, and purification response carries a disclaimers: list[Disclaimer]. Each disclaimer is versioned, severity-tagged, and deep-links to a specific section of the Halal Terminal legal page. You must render these in any user-facing surface — the API ships your compliance copy for you.
from halalterminal import Client
ht = Client(api_key="ht_…")
result = ht.screen("AAPL")
for d in result.disclaimers:
print(d.id) # "screening"
print(d.version) # "2026-05-12" — ISO date; advances when text is edited
print(d.severity) # "religious" (fatwa caveats) | "data" (freshness/sourcing)
print(d.lang) # "en"
print(d.text) # "Methodology-based screen, not a fatwa."
print(d.url) # deep link to the specific legal section
# You can also fetch the full public disclaimer registry (no API key required)
ht_public = Client()
all_disclaimers = ht_public.get_disclaimers()Compare d.version against what your UI last displayed and re-render when it advances.
The Halal Terminal API surfaces 60+ endpoints. Any path not yet covered by a typed method is reachable via the low-level helpers:
from halalterminal import Client
ht = Client(api_key="ht_…")
# GET — returns parsed JSON (dict, list, or scalar)
trending = ht.get("/api/trending")
print(trending) # [{"symbol": "AAPL", …}, …]
# GET with query parameters
etf = ht.get("/api/etf/HLAL", params={"look_through": "true"})
# POST — returns parsed JSON
report = ht.post("/api/reports/portfolio", json={"symbols": ["AAPL", "MSFT"]})
# Both methods raise the same typed exceptions on non-2xx responsesClient(
api_key: str | None = None,
*,
base_url: str = "https://api.halalterminal.com",
timeout: float = 30.0,
session: requests.Session | None = None,
)| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str | None |
None |
Your ht_… key. Falls back to HALAL_TERMINAL_API_KEY env var if omitted. |
base_url |
str |
"https://api.halalterminal.com" |
Override for staging or self-hosted deployments. Trailing slash is stripped automatically. |
timeout |
float |
30.0 |
Per-request timeout in seconds. |
session |
requests.Session | None |
None |
Provide a pre-configured session (e.g. with retries). A fresh session is created if omitted. |
| Method | Returns | Description |
|---|---|---|
screen(symbol, *, force_refresh=False) |
ScreeningResult |
Shariah compliance screen for a single ticker. Set force_refresh=True to bypass the server-side cache. |
get_quote(symbol) |
Quote |
Real-time price, change, volume, and market cap. |
scan_portfolio(symbols) |
PortfolioScanResult |
Bulk compliance scan. symbols is any iterable of ticker strings. |
calculate_zakat(holdings, *, gold_price_per_gram=None) |
ZakatResult |
Compute zakat liability on stock holdings. Each holding is {"symbol": str, "market_value": float}. |
get_disclaimers() |
list[Disclaimer] |
Fetch the canonical disclaimer registry. No API key required. |
health() |
dict |
API liveness check. No API key required. |
get(path, params=None) |
Any |
Generic GET. Returns parsed JSON. |
post(path, json=None) |
Any |
Generic POST. Returns parsed JSON. |
ScreeningResult
| Attribute | Type | Description |
|---|---|---|
symbol |
str |
Ticker symbol (uppercased). |
is_compliant |
bool | None |
True / False / None (insufficient data). |
shariah_compliance_status |
str | None |
"compliant" | "non_compliant" | "questionable". |
business_screen_pass |
bool | None |
Whether the business-activity screen passed. |
financial_screen_pass |
bool | None |
Whether the financial-ratio screen passed. |
purification_rate |
float | None |
Fraction of dividends to purify (e.g. 0.009 = 0.9%). |
compliance_explanation |
str | None |
Plain-English verdict summary. |
by_methodology |
dict[str, dict] |
Per-methodology breakdown keyed by "AAOIFI", "DJIM", "FTSE", "MSCI", "SP500S". |
disclaimers |
list[Disclaimer] |
Inline compliance copy. |
raw |
dict |
Full API response for fields not yet promoted to typed attributes. |
Quote
| Attribute | Type | Description |
|---|---|---|
symbol |
str |
Ticker symbol. |
name |
str |
Company name. |
price |
float |
Latest price. |
change |
float |
Absolute price change. |
change_percent |
float |
Percentage change. |
volume |
int |
Trading volume. |
market_cap |
float | None |
Market capitalisation (may be None for smaller stocks). |
disclaimers |
list[Disclaimer] |
Inline data-freshness copy. |
raw |
dict |
Full API response. |
ZakatResult
| Attribute | Type | Description |
|---|---|---|
total_market_value |
float |
Sum of all holding market values. |
nisab_threshold |
float |
Nisab in the same currency as the holdings. |
is_above_nisab |
bool |
Whether zakat is due (total_market_value >= nisab_threshold). |
total_zakat |
float |
Zakat liability (2.5% of eligible assets if above nisab, else 0). |
disclaimers |
list[Disclaimer] |
Religious and calculation caveats. |
raw |
dict |
Full API response including per-holding detail. |
PortfolioScanResult
| Attribute | Type | Description |
|---|---|---|
total |
int |
Total number of symbols scanned. |
compliant_count |
int |
Count of compliant symbols. |
non_compliant_count |
int |
Count of non-compliant or questionable symbols. |
disclaimers |
list[Disclaimer] |
Inline compliance copy. |
raw |
dict |
Full API response including raw["results"] for per-symbol detail. |
Disclaimer
| Attribute | Type | Description |
|---|---|---|
id |
str |
Machine-readable identifier (e.g. "screening", "zakat"). |
text |
str |
Human-readable disclaimer text. |
url |
str |
Deep link to the specific section of the legal page. |
version |
str |
ISO date string — advances each time the text is edited. |
lang |
str |
Language code (currently always "en"). |
severity |
str |
"religious" (fatwa/scholar caveats) | "data" (freshness/sourcing). |
All exceptions inherit from HalalTerminalError and carry .status_code, .code, and .detail.
| Exception | HTTP status | Trigger |
|---|---|---|
ApiKeyError |
401 / 403 | Key missing, invalid, expired, or deactivated. |
NotFoundError |
404 | Symbol or resource not found. |
QuotaExceededError |
429 (QUOTA_EXCEEDED) |
Monthly token allowance exhausted. .detail contains the upgrade hint. |
RateLimitError |
429 (other) | Upstream rate limiter. Retry with backoff. |
ServerError |
5xx | Server-side failure. Safe to retry with exponential backoff. |
HalalTerminalError |
any other 4xx | Catch-all for unexpected non-2xx responses. |
The SDK is synchronous. To use it inside an async application, run blocking calls in a thread pool:
import asyncio
from halalterminal import Client
ht = Client(api_key="ht_…")
async def get_screening(symbol: str):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, ht.screen, symbol)
result = asyncio.run(get_screening("AAPL"))
print(result.is_compliant)Or use asyncio.to_thread (Python 3.9+):
result = await asyncio.to_thread(ht.screen, "AAPL")The test suite is fully offline — HTTP is mocked via responses:
pip install -e ".[dev]"
pytestTo inject a custom requests.Session (e.g. for integration testing against a staging server):
import requests
from halalterminal import Client
session = requests.Session()
session.verify = False # staging cert, for example
ht = Client(api_key="ht_staging_…", base_url="https://staging.api.halalterminal.com", session=session)- API reference
- Sukuk screening guide
- Shariah-compliant ETFs compared (2026)
- Is my stock halal? Screener
Website · API · JS SDK · MCP server · Claude plugin · Discord bot · TradingView indicator · Portfolio tracker
MIT. © Halal Terminal. See LICENSE.
Part of the Halal Terminal open ecosystem: API · MCP server · Python SDK · JS SDK · Datasets · Awesome Islamic Finance