Skip to main content

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

StatusSuggested UX
compliantGreen badge, show ratios
non-compliantRed badge, show which screen failed
pendingSpinner 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.