/v1, we only add fields, endpoints and enum
values. See Versioning.
methodologynow readsaaoifi-ss21@2026.3on every result screened under AAOIFI Standard 21. It names the method we screen with today.- An undisclosed prohibited line is sized, never failed at the segment that holds it. When a
company does not report a prohibited line, we size it from named evidence: its own figure, a
named estimate for the period, a construction from named inputs, a peer’s share, or a
registered default. A line nothing can size after a recorded search counts at zero. A range
that crosses 5% is still non-compliant, so
determination.statuscan move either way on a re-screen. - A grocer’s pork counts bacon, ham and sausage, not fresh pork alone, and a theme park passes the activity screen, with its shows and parades counted as prohibited income.
revenue.prohibited.amountandrevenue.permissible.amountare corrected. The percentages divide revenue plus interest, but the amounts multiplied them by revenue alone, so interest read low. A stock that reports $79.6M of interest now shows $79.6M. The percentages and determinations don’t change.revenue.total_income. The figure the percentages divide.revenue.totalstays reported revenue.- Evidence Packet
prohibited_revenue.formulareadsprohibited_income / total_incomeand the denominator is labelledTotal Incomewhen interest sits outside revenue, so the numerator divided by the denominator gives the screen’s ratio from the reported interest.
POST /v1/analysis-requests. Ask us to analyze a stock that has no result yet, the same request the “Request analysis” button on a stock page makes. It needs the Plus or Developer plan, and each new request counts against your account’s on-demand analysis allowance, 10 a day with up to 3 running at once. If an analysis of the stock is already running, you join it. Send"notify": trueto be emailed when we publish the result; by default we don’t email. See Request an analysis.GET /v1/analysis-requests/{request_id}. Follow a request to its result:statusmoves fromqueuedorscheduledthroughrunningtocomplete, anddeterminationhas the published status. A fund, a trust or a run our team must finish isunder_reviewuntil then.- New error codes.
plan_required,already_analyzed,listing_not_active,no_filings,stale_filings,withheldandanalysis_limit_reached, all from the new endpoint. See Error handling.
- A sandbox refusal carries
error.upgrade. Every403 sandbox_restrictedand every429on the sandbox has anupgradeobject: the Plus plan’s monthly price and link, and the Developer plan’s, so your code can show them. Its fields are in the error schema. A caller on a paid plan gets noupgrade. - The same errors say what the Plus plan includes and costs. The
messageends with the Plus plan’s price and link, then the Developer plan’s, for anyone reading the error. Error codes are unchanged: match oncode, never onmessage.
- The Developer plan allows 10,000 requests a day, up from 5,000. The
limit is per account, shared by every live key, connected app and
signed-in session on it. The per-minute limit stays at 300, and Evidence
Packets stay unlimited.
GET /v1/mereports the new limit inlimits.requests_per_day.
- The Developer plan. A commercial license to show halal.sh screening results in your own product: the compliance status, the ratios, the explanation and the Evidence Packet, with the credit Screening by halal.sh beside them. It’s $99 a month and includes the full stock and ETF universe, 5,000 requests a day, 300 a minute and unlimited Evidence Packets. The limits are per account: every live key, connected app and signed-in session on it shares them. It’s a subscription of its own and doesn’t include Plus: on Plus, you add the Developer plan as a second subscription and keep your keys. Subscribe from halal.sh/developers. What the license covers is in Licensing.
GET /v1/mereportsplan: developeron the Developer plan, with its limits inlimits.plantakesfree,plus,developerorenterprise.GET /v1links the Developer plan’s license inlicensing.- Clearer messages. Error messages, the Evidence Packet’s
disclosures, the health ratios’descriptionand the methodology’spurification_descriptionare reworded to match what the API does. Error codes and field names are unchanged: match oncode, never onmessage. - Four Evidence Packet stability fields are deprecated.
stability.quarters_compliant,stability.quarters_totalandstability.flip_rateare alwaysnull, andstability.closest_threshold.trendis alwaysstable. They stay in the packet throughout/v1and will be removed in/v2. For how a stock’s status has changed across its filings, readGET /instruments/{symbol}/history. See Deprecated fields.
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 analyzed 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 analyzed 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 $99.4 billion of interest against $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 analyzed the instrument, and the date we recorded it. Qatar Islamic Bank (QSE:QIBK) was screened from its audited statements and readsscreen_unresolved, not “not analyzed”. The field isnulleverywhere else andstatusstayspending, so existing clients are unaffected. MCPscreen_stockreports it. See When we analyzed 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 returns the string itself.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 unanalyzed 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 analyzed 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 no longer come back as unanalyzed stocks. Around 180 fund
tickers also carry an equity row from crawler seeding, so
POST /screenanswered forSPY,QQQand other funds 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}/evidencehonors the exchange prefix. July’s entry below said it did. Only half of that change went live: 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-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 analyzed. 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 honors 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 containing “a%e” rather than three unrelated tickers.
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 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 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.
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.1to2026.2) - Breaking-change notices with migration guides (new major versions only)
- Coverage-expansion updates

