Overview
When you query a stock that hasn’t been analyzed yet, the API automatically queues analysis and returns status: "pending". This guide shows how to handle that flow in your app.
The flow
1. Your app calls GET /instruments/PLTR/compliance
2. API returns { determination: { status: "pending" } }
3. Your app shows a loading state
4. Your app polls the same endpoint every ~10 seconds
5. API returns { determination: { status: "compliant" }, screens: { ... } }
6. Your app renders the result
Most stocks are pre-analyzed and return instantly. You’ll only hit pending for less common tickers.
Implementation
Node.js / TypeScript
async function getCompliance(symbol: string): Promise<ComplianceData> {
const response = await fetch(
`https://api.halal.sh/v1/instruments/${symbol}/compliance`,
{ headers: { "X-API-Key": process.env.HALAL_API_KEY } }
);
const { data, meta } = await response.json();
if (data.determination.status === "pending") {
const retryAfter = meta.retry_after ?? 10;
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return getCompliance(symbol); // Poll again
}
return data;
}
Python
import time
import requests
def get_compliance(symbol: str) -> dict:
response = requests.get(
f"https://api.halal.sh/v1/instruments/{symbol}/compliance",
headers={"X-API-Key": API_KEY}
)
result = response.json()
data = result["data"]
meta = result.get("meta", {})
if data["determination"]["status"] == "pending":
retry_after = meta.get("retry_after", 10)
time.sleep(retry_after)
return get_compliance(symbol)
return data
React
function StockCompliance({ symbol }) {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
useEffect(() => {
let cancelled = false;
async function fetchCompliance() {
const res = await fetch(`/api/compliance/${symbol}`);
const { data, meta } = await res.json();
if (cancelled) return;
if (data.determination.status === "pending") {
setIsPending(true);
const retryAfter = (meta.retry_after ?? 10) * 1000;
setTimeout(() => {
if (!cancelled) fetchCompliance();
}, retryAfter);
return;
}
setIsPending(false);
setData(data);
}
fetchCompliance();
return () => { cancelled = true; };
}, [symbol]);
if (isPending) return <p>Analyzing {symbol}...</p>;
if (!data) return null;
return (
<div>
<p>Status: {data.determination.status}</p>
<p>Confidence: {data.determination.confidence}</p>
</div>
);
}
Batch screening with pending stocks
When you screen multiple symbols, some may come back as pending:
{
"data": {
"results": [
{ "symbol": "AAPL", "status": "compliant", "confidence": 0.92 },
{ "symbol": "MSFT", "status": "compliant", "confidence": 0.95 },
{ "symbol": "NEWCO", "status": "pending" }
],
"summary": {
"total": 3,
"compliant": 2,
"non_compliant": 0,
"pending": 1
}
}
}
To resolve pending stocks, poll their individual compliance endpoints:
const results = await screenPortfolio(symbols);
const pending = results.filter((r) => r.status === "pending");
// Resolve pending stocks individually
const resolved = await Promise.all(
pending.map((r) => getCompliance(r.symbol))
);
What to show users
| Status | Suggested UX |
|---|
compliant | Green badge, show ratios |
non-compliant | Red badge, show which screen failed |
pending | Spinner or “Analyzing…” with the stock name |
Analysis typically completes within 2-5 minutes.
Don’t poll more frequently than meta.retry_after suggests. Aggressive polling won’t speed up analysis and may hit rate limits.