> ## Documentation Index
> Fetch the complete documentation index at: https://docs.halal.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Request an Analysis

> Ask us to analyze a stock that has no compliance result yet. This is
the request the "Request analysis" button on a stock page makes, and
it needs the Plus or Developer plan.

We run one analysis per stock however many people ask. If one is
already running, you join it: the reply is `200` with
`joined_existing: true` and that run's `request_id`. A new request
gets `202`. Either way, poll `links.self` until `status` is
`complete`, then read the result at `links.compliance`. We don't
email you about the request unless you send `"notify": true`; with it,
we email the account when we publish the result, or if the analysis
goes to our compliance team for review.

Each new request counts against your account's on-demand analysis
allowance, the same one the website uses: 10 a day on the Plus and
Developer plans, with up to 3 running at once. Joining a run already
in progress doesn't count. Past the allowance the reply is `429
analysis_limit_reached`, and `retry_after` is the number of seconds
until you can ask again. Send an account's requests one at a time: we
accept one every 2 seconds per account, and a faster one gets `429
rate_limit_exceeded`.

When we can't analyze the stock, we refuse it with one of these codes:

- `409 already_analyzed`: the stock already has a result. Read it
  from `/instruments/{symbol}/compliance`. A secondary share class
  whose primary we've analyzed gets this too, with the primary in
  `error.instrument_id`.
- `422 no_filings`: we hold no filings we can screen the company
  from.
- `422 stale_filings`: its latest annual report is too old for a
  reliable first analysis.
- `422 withheld`: we've reviewed the company and can't publish a
  verdict yet; the reason is in `message`.
- `409 listing_not_active`: the listing is delisted or inactive; its
  status and delisting date are in `error.status` and
  `error.delisted_at`.

We accept funds, trusts and other pooled vehicles, and our compliance
team reviews them, so they come back as `under_review`.




## OpenAPI

````yaml POST /analysis-requests
openapi: 3.1.0
info:
  title: halal.sh API
  version: 1.0.0
  description: |
    The halal.sh API screens public equities and ETFs for Shariah compliance,
    based on AAOIFI Shari'ah Standard No. 21.

    Every determination includes the individual screens, the financial ratios
    with their thresholds, the revenue breakdown, and the source filings behind
    each number. The Evidence Packet endpoint returns the full audit trail: each
    screen's calculation, the values it used and the filing they came from.

    ## Conventions

    - **Base URL**: `https://api.halal.sh/v1`
    - **Authentication**: send your key in the `X-API-Key` header.
    - **Envelope**: every success response is `{ "data": …, "meta": … }`. Every
      error is `{ "error": { "code", "message" } }`.
    - **Ratios**: screen `value`, `threshold`, and `buffer` are decimals
      (`0.082` means 8.2%, `0.30` means 30%). Fields named `*percentage`,
      `purity.percentage`, and holding `weight` are whole-number percentages
      (`66.74` means 66.74%). Each field's description states its unit.
    - **Status**: compliance status is always `compliant`, `non-compliant`, or
      `pending`. Treat unknown future values as `pending`.
    - **Money**: amounts are in US dollars, converted for a company that reports
      in another currency.
  contact:
    name: halal.sh API support
    url: https://halal.sh/developers
    email: api@halal.sh
servers:
  - url: https://api.halal.sh/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: Instruments
    description: Compliance, financials, evidence, history, and health for a single stock.
  - name: Analysis requests
    description: >-
      Ask for an analysis of a stock we haven't screened yet, and follow it to
      the result.
  - name: Screening
    description: Batch screening and search across the instrument universe.
  - name: ETFs
    description: Shariah purity and holdings breakdown for ETFs.
  - name: Methodology
    description: The screening methodology, its thresholds, and version.
  - name: Account
    description: Who is calling, on what plan, and where everything is.
paths:
  /analysis-requests:
    post:
      tags:
        - Analysis requests
      summary: Request an analysis
      description: |
        Ask us to analyze a stock that has no compliance result yet. This is
        the request the "Request analysis" button on a stock page makes, and
        it needs the Plus or Developer plan.

        We run one analysis per stock however many people ask. If one is
        already running, you join it: the reply is `200` with
        `joined_existing: true` and that run's `request_id`. A new request
        gets `202`. Either way, poll `links.self` until `status` is
        `complete`, then read the result at `links.compliance`. We don't
        email you about the request unless you send `"notify": true`; with it,
        we email the account when we publish the result, or if the analysis
        goes to our compliance team for review.

        Each new request counts against your account's on-demand analysis
        allowance, the same one the website uses: 10 a day on the Plus and
        Developer plans, with up to 3 running at once. Joining a run already
        in progress doesn't count. Past the allowance the reply is `429
        analysis_limit_reached`, and `retry_after` is the number of seconds
        until you can ask again. Send an account's requests one at a time: we
        accept one every 2 seconds per account, and a faster one gets `429
        rate_limit_exceeded`.

        When we can't analyze the stock, we refuse it with one of these codes:

        - `409 already_analyzed`: the stock already has a result. Read it
          from `/instruments/{symbol}/compliance`. A secondary share class
          whose primary we've analyzed gets this too, with the primary in
          `error.instrument_id`.
        - `422 no_filings`: we hold no filings we can screen the company
          from.
        - `422 stale_filings`: its latest annual report is too old for a
          reliable first analysis.
        - `422 withheld`: we've reviewed the company and can't publish a
          verdict yet; the reason is in `message`.
        - `409 listing_not_active`: the listing is delisted or inactive; its
          status and delisting date are in `error.status` and
          `error.delisted_at`.

        We accept funds, trusts and other pooled vehicles, and our compliance
        team reviews them, so they come back as `under_review`.
      operationId: requestAnalysis
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnalysisRequestBody'
            example:
              symbol: TYO:7203
              notify: true
      responses:
        '200':
          description: An analysis of this stock was already running, and you've joined it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalysisRequestCreatedResponse'
        '202':
          description: We've accepted the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalysisRequestCreatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |
            Requesting an analysis needs a paid plan, so a sandbox caller gets
            `plan_required`. The Plus plan's price is in `message`, and
            `error.upgrade` has the same offer as fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: plan_required
                  message: >-
                    Requesting an analysis needs a paid plan. The Plus plan lets
                    you request an analysis of any stock we have not screened
                    yet, for $7 a month:
                    https://halal.sh/plans?source=api_refusal. To show results
                    in your own product, you need the Developer plan, at $99 a
                    month: https://halal.sh/developers.
                  upgrade:
                    plan: plus
                    price_usd_monthly: 7
                    url: https://halal.sh/plans?source=api_refusal
                    commercial_plan: developer
                    commercial_price_usd_monthly: 99
                    commercial_url: https://halal.sh/developers
                meta:
                  request_id: req_abc123
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: >-
            The stock already has a result (`already_analyzed`), or the listing
            is delisted or inactive (`listing_not_active`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: already_analyzed
                  message: This stock already has compliance analysis.
                meta:
                  request_id: req_abc123
        '422':
          description: >-
            We can't analyze this stock (`no_filings`, `stale_filings` or
            `withheld`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: no_filings
                  message: >-
                    We can't analyze this stock because we hold no filings we
                    can screen it from.
                meta:
                  request_id: req_abc123
        '429':
          description: |
            Your account's on-demand analysis allowance is used up
            (`analysis_limit_reached`), or you sent a second request within 2
            seconds or hit your plan's rate limit (`rate_limit_exceeded`).
            `Retry-After` and `error.retry_after` hold the wait in seconds.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait before asking again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: analysis_limit_reached
                  message: >-
                    You already have 3 analyses in progress. Please wait for one
                    to finish before requesting another.
                  retry_after: 2100
                  limit_type: in_progress
                  limit: 3
                  current: 3
                  reset_at: '2026-11-02T13:35:00.000Z'
                meta:
                  request_id: req_abc123
components:
  schemas:
    AnalysisRequestBody:
      type: object
      required:
        - symbol
      properties:
        symbol:
          type: string
          description: >-
            The stock, in the form `/instruments/{symbol}` accepts. A bare
            symbol is a US listing; prefix any other market's symbol, as in
            `TYO:7203`.
          example: TYO:7203
        notify:
          type: boolean
          default: false
          description: >-
            Email the account when we publish the result, or if the analysis
            goes to our compliance team for review. Off by default, because a
            program polling for results doesn't need an email per stock. When
            you join an analysis someone else started, it covers only the email
            when we publish the result, and it applies to you alone. While a
            request you've made is still running, sending it again with `true`
            turns this on for it.
    AnalysisRequestCreatedResponse:
      type: object
      properties:
        data:
          allOf:
            - $ref: '#/components/schemas/AnalysisRequest'
            - type: object
              required:
                - joined_existing
              properties:
                joined_existing:
                  type: boolean
                  description: >-
                    True when an analysis of this stock was already running and
                    you've joined it.
        meta:
          allOf:
            - $ref: '#/components/schemas/Meta'
            - type: object
              properties:
                estimated_minutes:
                  type: integer
                  description: >-
                    Our estimate of how long a new analysis will take, including
                    any wait for a Standard window. Absent when you joined a run
                    or the stock is under review.
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - bad_request
                - unauthorized
                - sandbox_restricted
                - plan_required
                - not_found
                - already_analyzed
                - listing_not_active
                - no_filings
                - stale_filings
                - withheld
                - rate_limit_exceeded
                - analysis_limit_reached
                - service_unavailable
                - internal_error
              example: not_found
            message:
              type: string
              example: Stock with identifier 'XYZ' not found
            retry_after:
              type: integer
              description: Seconds until you can retry (only on 429).
            limit_type:
              type: string
              enum:
                - daily
                - in_progress
              description: >-
                On `analysis_limit_reached`: which part of the allowance is used
                up, the day's analyses or those running at once.
            limit:
              type: integer
              description: 'On `analysis_limit_reached`: the allowance.'
            current:
              type: integer
              description: 'On `analysis_limit_reached`: how many you have used.'
            reset_at:
              type: string
              format: date-time
              description: 'On `analysis_limit_reached`: when you can ask again.'
            instrument_id:
              type: string
              description: >-
                On `already_analyzed` for a secondary share class: the primary,
                whose result applies.
            status:
              type: string
              description: >-
                On `listing_not_active`: the listing's status, such as
                `delisted`.
            delisted_at:
              type: string
              format: date-time
              nullable: true
              description: 'On `listing_not_active`: the date of the delisting.'
            upgrade:
              $ref: '#/components/schemas/UpgradeOffer'
        meta:
          $ref: '#/components/schemas/Meta'
    AnalysisRequest:
      type: object
      required:
        - request_id
        - instrument_id
        - status
        - lane
        - requested_at
        - promised_by
        - scheduled_for
        - completed_at
        - progress_percentage
        - determination
        - notify
        - links
      properties:
        request_id:
          type: string
          format: uuid
        instrument_id:
          type: string
          description: >-
            The listing the request is for, in the form `/instruments/{symbol}`
            accepts.
          example: TYO:7203
        status:
          type: string
          enum:
            - queued
            - scheduled
            - running
            - under_review
            - complete
          description: |
            - `queued`: waiting to start.
            - `scheduled`: on the Standard lane, waiting for `scheduled_for`.
            - `running`: we're analyzing it.
            - `under_review`: with our compliance team before we publish the
              result. Every fund and trust goes through it, and so does a run
              that hits a problem, which the team then finishes.
            - `complete`: published. `determination` has the status.

            Treat an unknown future value as still in progress.
        lane:
          type: string
          enum:
            - priority
            - standard
          description: >-
            `priority` starts now, and we promise it within 2 hours. `standard`
            waits for a window, and we promise it within 2 days.
        requested_at:
          type: string
          format: date-time
          nullable: true
        promised_by:
          type: string
          format: date-time
          nullable: true
          description: When we said the analysis would be ready.
        scheduled_for:
          type: string
          format: date-time
          nullable: true
          description: >-
            When a Standard-lane request starts. Null on the Priority lane, and
            once the request is under review or complete.
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: When we saved the result. Null until `status` is `complete`.
        progress_percentage:
          type: integer
          minimum: 0
          maximum: 100
          description: How far the analysis has got, as a whole-number percentage.
        determination:
          type: string
          enum:
            - compliant
            - non-compliant
            - pending
          nullable: true
          description: >-
            The published compliance status once `status` is `complete`, else
            null.
        notify:
          type: boolean
          description: >-
            Whether we will email you about this request, as you set it with
            `notify`.
        links:
          type: object
          required:
            - self
            - compliance
          properties:
            self:
              type: string
              example: /v1/analysis-requests/6f1c2d4e-0000-4000-8000-000000000001
            compliance:
              type: string
              example: /v1/instruments/TYO:7203/compliance
    Meta:
      type: object
      properties:
        request_id:
          type: string
          description: Unique ID for this request. Include it in support requests.
          example: req_abc123
        as_of:
          type: string
          format: date-time
          description: When this response was generated.
        methodology:
          type: string
          description: >
            Methodology used, as `id@version`. Present on compliance responses.

            `aaoifi-ss21` for an operating company; `aaoifi-ss57`, `aaoifi-ss20`
            or

            `halal-sh-etf-taxonomy` for a pooled vehicle decided on its
            structure

            (see `screens.structural` on the compliance resource).
          example: aaoifi-ss21@2026.1
    UpgradeOffer:
      type: object
      description: |
        The paid plans that remove a sandbox limit, with their prices. Present
        on every `sandbox_restricted` 403 and on a sandbox caller's 429, and
        absent for a caller already on a paid plan.
      required:
        - plan
        - price_usd_monthly
        - url
        - commercial_plan
        - commercial_price_usd_monthly
        - commercial_url
      properties:
        plan:
          type: string
          enum:
            - plus
          description: 'The plan for your own use: live keys, the full universe, MCP.'
        price_usd_monthly:
          type: number
          example: 7
        url:
          type: string
          format: uri
          example: https://halal.sh/plans?source=api_refusal
        commercial_plan:
          type: string
          enum:
            - developer
          description: The plan for showing results in your own product.
        commercial_price_usd_monthly:
          type: number
          example: 99
        commercial_url:
          type: string
          format: uri
          example: https://halal.sh/developers
  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: bad_request
              message: Maximum 50 symbols per request.
            meta:
              request_id: req_abc123
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: Invalid or revoked API key.
            meta:
              request_id: req_abc123
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: not_found
              message: Stock with identifier 'XYZ' not found
            meta:
              request_id: req_abc123
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.