/v1, we only ever add fields, endpoints, and
enum values. See Versioning.
Every listing we hold is reachable by its venue, the API describes itself, and a gold vehicle names its own methodology
- Sixteen more exchanges have a prefix. Listings on Borsa Istanbul
(
IST), Tel Aviv (TLV), Ho Chi Minh City (HOSE), the Philippines (PSE), Boursa Kuwait (KWSE), Santiago (BCS), Athens (ATH), Egypt (EGX), Colombia (BVC), Nasdaq Iceland (ICE), Moscow (MCX), Lima (BVL), Casablanca (CAS), Prague (PRG), Nigeria (NGX) and Bucharest (BVB) readIST:THYAOinstead of a bare ticker that a lookup resolves to a US listing first. The prefix follows the venue only, so a company from those countries listed on Nasdaq or NYSE keeps its bare ticker, and every identifier issued before still reaches the same listing. See Instrument identifiers. - The Evidence Packet names the XBRL concepts behind its values.
sources[].xbrl_tagslists the concepts whose facts, for the period each value measures, add up to each value in the source’sraw_valuesexactly. Tags appear as results are analysed again: Apple’s debt screen will then readLongTermDebtNoncurrent,LongTermDebtCurrentandCommercialPaper. The list stays empty for a value we could not rebuild from the filings’ facts, for one that rests on a subtraction, for one taken from another source, and for results analysed before this release. See Evidence Packets. - Credicorp, SFL and Ardmore Shipping read as their NYSE tickers.
instrument_idisBAP,SFLandASC. They were stored as Danish companies and readCPH:BAP,CPH:SFLandCPH:ASC, which named a Copenhagen listing that does not exist; those ids now return 404. GET /instruments/{symbol}reports the sameinstrument_idas the other endpoints. A listing addressed through its venue, such as ANTA (HKG:2020), read a bare ticker there; it andexchangenow come from the listing itself.- A company with no revenue shows its interest as prohibited income.
With
revenue.total0 and a prohibited share above zero,revenue.prohibited.amountandbreakdown.interest_incomecarry the interest income the screen fails on; they read 0 before. - Every instrument response names its listing.
/compliance,/financials,/historyand/healthaddinstrument_idbesidesymbol, as the overview and search already did:symbolalone can name two companies (AIis C3.ai in the US and Air Liquide in Paris). The MCP tools lead their summaries with it. - The prohibited-revenue breakdown sums to its amount.
revenue.prohibited.breakdownaddsother: prohibited revenue in none of the six named categories, such as a conventional bank’s underwriting, card and deposit fees. JPMorgan’s breakdown named 138.3 billion prohibited. A named category other than interest is filled only when the analysis recorded it separately; otherwise that revenue is inother. - ETF purity dates its holdings.
purity.holdings_as_ofonGET /etfs/{symbol}is the date of the holdings snapshot the figure weighs.as_ofstays the time of the calculation, which for a live figure is the request itself. - A stock ticker sent to
/etfsnames the right endpoint. The 404 says'PAR:AI' is a stock, not a fundand points to/instruments/{symbol}/compliance, as/instrumentsalready points a fund ticker to/etfs. - Rate-limit refusals use the error envelope. The per-IP limit on
requests before key validation and the limit on
GET /v1and the OpenAPI document answered429with a different body. They now returnerror.code: rate_limit_exceededwithretry_afterandmeta.request_id, like every other error. - A
pendingdetermination says when we hold the verdict back.GET /instruments/{symbol}/complianceaddsdetermination.withheldandGET /instruments/{symbol}addscompliance.withheld: the reason category, whether we analysed the instrument, and the date we recorded it. Qatar Islamic Bank (QSE:QIBK) was screened from its audited statements and readsscreen_unresolved, not “not analysed”. The field isnulleverywhere else andstatusstayspending, so existing clients are unaffected. MCPscreen_stockreports it. See When we analysed it and hold the verdict back. - A pooled vehicle names the methodology it was decided under. A gold
or silver trust, a London-listed physical-metal ETC (
LON:SGLN,LON:SSLN,LON:RMAP) or a futures pool is decided on the structure of the vehicle, not on ratios, andGET /instruments/{symbol}/compliancepublished every one of them asaaoifi-ss21@2026.1with three passing ratio screens at 0%. Such a result now carriesaaoifi-ss57@2026.1,aaoifi-ss20@2026.1orhalal-sh-etf-taxonomy@2026.1inmethodologyandmeta.methodology, reports the three ratio screens asnot_applicable, and addsscreens.structural: the criteria, each verdict and the quoted evidence behind it. The Evidence Packet carries one screen per criterion with the same quotes,/financialsreports the ratios asnot_applicable, andGET /methodologieslists the three structural methodologies with their criteria. MCPscreen_stockrenders the checklist andget_methodologytakes the new ids. An operating company’s response is unchanged. See Pooled vehicles. - A prefix names the listing venue, whatever the company’s domicile.
556 of the 653 Hong Kong listings we cover are Cayman, PRC, Bermuda or BVI
companies, and a strict country match made every one of them a
404throughHKG:.HKG:2020(ANTA),HKG:1088(China Shenhua) andHKG:1211(BYD) now resolve, as do Jersey and Guernsey companies on the LSE throughLON:, and Cayman companies on the TWSE throughTPE:. A Hong Kong code also answers to any zero-padding (HKG:992,HKG:0992,HKG:00992all reach Lenovo). - Alternative venue spellings resolve. Google Finance’s codes
(
EPA:AI,EBR:UCB,ETR:SAP,BIT:ENI), the venues’ own names (HKEX:0669,LSE:AZN,NZX:FPH,BME:AMS,XETRA:SAP) and a US venue written as a prefix (NASDAQ:AAPL) are all accepted.TSE:reads a numeric ticker as Tokyo and an alphabetic one as Toronto. instrument_idon every instrument and search hit. The exact string to send to/instruments/{symbol}.symbol+country_codecould not always be turned into a working request (a Cayman domicile has no prefix of its own); now the API says it outright.POST /screenaccepts exchange-prefixed identifiers. August’s entry below made it400on a prefix because the route’s existence check matched raw strings. The check now resolves through the same parser as/instruments, so a European or Hong Kong portfolio screens in one call, each row echoing the identifier in upper case.- The API describes itself.
GET /v1lists every endpoint and the docs;GET /v1/openapi.json(and.yaml) serves this site’s OpenAPI document, so an agent framework can load it by URL; neither needs a key.GET /v1/mereports the caller’s plan, key, limits and today’s usage split into direct and MCP traffic, so a lapsed plan is distinguishable from a bad key without opening the dashboard. GET /instruments/{symbol}/healthsays why a non-US listing has none. Financial health is computed from SEC filings, so a listing that does not file with the SEC has no health analysis; the404now says so instead of reading like a wrong identifier. The compliance determination is unaffected.- Error codes match the reference. A
400carriesbad_request(it saidvalidation_error). A spent sandbox Evidence Packet quota answers403 sandbox_restricted, and a quota check that cannot reach its store answers503 service_unavailable; both answered403 forbidden. An unknown path answers in the/v1error envelope with arequest_id. A body that is not valid JSON answers400instead of500, and/mcpanswers it as a JSON-RPC parse error (-32700). - Rate limits follow your plan on every route. A per-IP limit meant for
GET /v1and the OpenAPI document ran on every/v1route: it held Plus and Enterprise keys to 60 requests a minute and put every MCP user under one shared limit. It now covers those discovery routes only. A ceiling of 600 requests a minute per IP, above every plan’s own limit, stays in front of key validation. POST /screenfilter mode.status: "pending"selects unanalysed stocks (it returned every stock). Alimitoroffsetthat is not a whole number answers400. Each row addsinstrument_idandcountry_code: a bareBAcan be Boeing or BAE Systems, andinstrument_idis what to send to/instruments/{symbol}. A sandbox key’s symbol list accepts a venue spelling of a sandbox stock, such asNASDAQ:AAPL.GET /etfs/LON:ISWDresolves, the form this reference and the MCPget_etftool give. A non-US prefix never answers with a US-listed fund, or the reverse.- The Evidence Packet for a trust decided on its structure carries no
ratio screens. GLD, IAU, IBIT and 26 other trusts analysed before the
checklist was stored came back with three ratio screens at zero; they now
carry the business-activity screen only, as
/compliancedoes.meta.methodologynames the result’s methodology on/compliance,/financialsand/evidence, andxbrl_tagsno longer lists the guessed tags it carried before. MCPget_evidence_packetreturns a packet with no filing period instead of an output validation error, and/mcpanswers an unknown or revoked key with401 invalid_tokenwhen the client connects. - The Evidence Packet names its listing.
instrumentaddsinstrument_id(PAR:AI, what to send to/instruments/{symbol}) and fillsexchange, andas_ofis the date of the analysis, as on/compliance, not the day the packet was built. - Filter-mode
POST /screentakescountryandsort.countrynarrows to a country of domicile (GB), andsort: "market_cap"lists the largest first; before, every page was alphabetical across every market. A page past the end reports the realmeta.totalrather than 0. - MCP
discover_stocks. Stocks that match a compliance status, sector, market-cap band, health rating or country, each with theinstrument_idto pass toscreen_stock. Plus plan.
Fund tickers route to the fund endpoint, and access follows your live plan
- Fund tickers no longer come back as unanalysed stocks. Around 180 fund
tickers also carry an equity row from crawler seeding, so
POST /screenanswered forSPY,QQQand friends with a permanentpending, andGET /instruments/{symbol}/compliancedid the same. A fund with no company determination is now reported as one:/screenreturns it inmeta.not_foundand names it in the newmeta.etfs, and the compliance endpoint addsmeta.etfwith the endpoint that does have the answer. Commodity and crypto trusts we screen under AAOIFI directly (GLD,IAU,SLV,IBIT,USO) still return their real determination. Additive: no field was removed or renamed. GET /etfs/{symbol}accepts a fund’s alternate listing line. A same-ISIN currency or exchange ticker (ISDWforISWD) resolved in the app but404ed here. It now resolves to the fund, as/searchalways implied it would.- Access follows your active subscription, not the credential. A
hsh_live_key minted while Plus was active kept the full universe after the plan ended. Universe, rate limits and the Evidence Packet quota are now all derived from the plan in force at request time. If Plus lapses, existing live keys keep working at sandbox scope and sandbox limits, and renewing restores full access within five minutes with no new key. A signed-in session is scoped the same way, by the account’s plan. - MCP
search_methodologyanswers with the relevant sub-section. It returned whole top-level knowledge-base sections, one of which is 43KB, so a single call could put roughly 50KB of mostly unrelated text into an agent’s context. It now returns the best-matching sub-sections within a size budget, each tagged with a dottedsectionpath, and takes a newsectionargument to read any one of them in full. - MCP
screen_portfoliopasses the newetfslist through, so an agent re-routes a fund toget_etfinstead of reporting it as an unknown ticker. GET /instruments/{symbol}/evidencereally does honour the exchange prefix now. July’s entry below said it did. Half of that change shipped: the cache key started including the country, but the route still called the builder without one, so every request resolved US-first and then cached that answer under the country-less key. A prefixed request (TYO:GS, GS Yuasa) returned Goldman Sachs’ packet, and so did the cache for the rest of its TTL. Now the country reaches the builder.- Non-US identifiers are documented, and a wrong one now corrects itself.
A non-US listing is addressed with an exchange prefix (
PAR:AI), not a vendor suffix (AI.PA), and nothing said so. Sending the vendor form now returns an error naming the prefixed form to use, and the format has a page of its own: Instrument identifiers. POST /screensays what it can resolve. It screens bare tickers only, and an exchange-prefixed identifier used to land silently inmeta.not_found, which reads as “we don’t cover that company”. It now returns400namingGET /instruments/{symbol}as the route that resolves it. The MCP tools’ symbol arguments were widened to 20 characters so a prefixed identifier reaches the instrument endpoints intact.
Cross-listing resolution and smarter search matching
- Cross-listings and secondary share classes now resolve to their company’s
data. Compliance results, health analyses and filings are stored once
against a company’s canonical listing. Requesting a different listing of the
same company (an ADR, a foreign line, or a second share class) previously
read that listing’s own (empty) rows, so
GET /instruments/{symbol}/healthand/historycould return404for a company we had fully analysed. Both now resolve to the canonical company and return the same answer for every listing of it. Responses still echo the symbol and name you asked for. Fixes-only: no field or shape changed. GET /instruments/{symbol}/evidencenow honours the exchange prefix. It previously ignored the country part of the identifier, so a prefixed symbol that collides with a US ticker (TYO:GSis GS Yuasa, not Goldman Sachs) could return the wrong company’s packet. Cached packets are keyed per listing.GET /searchmatches multi-word queries word-by-word. A query whose words are separated in the target name now matches:Technology fundfinds Vanguard Information Technology Index Fund, where it previously returned nothing. Whole-phrase matches still rank above scattered-word matches, so existing single-word queries return the same results in the same order.%and_in a search query are now literal characters, not SQL wildcards.a%ereturns funds and companies actually containing “a%e” rather than three unrelated tickers.
Search now returns ETFs
GET /searchnow returns ETFs alongside stocks. Every result carries atypediscriminator (stock|etf). Stock items are unchanged; ETF items expose Shariah purity (purity_percentage,badge_color) andmatch_coverage_percentinstead of a compliancestatus. This is additive: existing stock-only integrations keep working and can ignore ETF rows bytype.- New
typesfilter. Passtypes=stock(ortypes=etf) to narrow to one kind; omit it for both. Any value outside the{stock, etf}allow-list returns400. - The MCP
search_instrumentstool returns the mixed results automatically and tags each hit with itstype.
Personal API, MCP, and filter screening included with Plus
- Personal API, MCP, and universe-filter screening are now included with Plus. Live keys, full MCP access, and filter-mode screening all come with the single paid tier.
- The previously-planned consumer Pro tier is discontinued. (Building a product on halal.sh remains the separate developer plan.)
Key dashboard moved to halal.sh/account/api
- Key management has a new home. Create and revoke keys at halal.sh/account/api; halal.sh/developers is now the commercial overview page. Live keys come with the halal.sh Plus plan.
- The MCP setup guide moved to /mcp-server, because
docs.halal.sh/mcpis the hosted docs-MCP endpoint itself. - Setup guides for ChatGPT, Claude, Gemini CLI, Cursor, and VS Code on the MCP server page. The ChatGPT and Claude apps connect by signing in with a halal.sh account; no key handling needed.
v1 surface finalized
The
/v1 API surface is finalized and stable:- Instruments: overview, compliance, financials, Evidence Packet, compliance history, and financial health.
- Screening: batch screening by symbol, and search.
- ETFs: purity, paginated holdings with per-holding compliance, and purity history.
- Methodology: list and detail for
aaoifi-ss21@2026.1.
- New endpoint announcements
- Methodology version bumps (e.g.
aaoifi-ss21@2026.1→2026.2) - Breaking-change notices with migration guides (new major versions only)
- Coverage-expansion updates

