{
  "openapi": "3.1.0",
  "info": {
    "title": "HalalScreener API",
    "version": "1.0.0",
    "summary": "Shariah screening: stocks and ETFs under AAOIFI Standard 21, and 800+ cryptocurrencies under published scholarly opinion.",
    "description": "Programmatic access to HalalScreener's screening engine. Screen individual assets, list the universe, and retrieve detailed ratio data. Designed for fintechs, wealth platforms, and Islamic banks integrating halal compliance checks into their products.\n\n**Authentication:** Bearer API keys. Create from the HalalScreener dashboard (`/developers`). Keys are stored hashed (SHA-256) at rest.\n\n**Rate limits:** Per-tier, enforced by Upstash Redis. Headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` are returned on every response. Exceeding the limit returns `429` with `Retry-After`.\n\n**Data source:** Financial Modeling Prep + SEC EDGAR, refreshed daily. Crypto via CoinGecko.\n\n**Shariah methodology:** AAOIFI Standard No. 21. Full methodology at https://halalscreener.app/en/methodology. Allowances: Free 3 calls in all (verdict, score, grade), Pro 1,000 a day, Premium 10,000 a day, Business 10,000 a month, Enterprise 100,000 a month; partner keys are not metered. Every response carries X-RateLimit-* headers; metered keys also carry X-Quota-*.",
    "contact": {
      "name": "HalalScreener API Support",
      "email": "support@halalscreener.app",
      "url": "https://halalscreener.app/en/developers"
    },
    "license": {
      "name": "Commercial",
      "url": "https://halalscreener.app/en/terms"
    }
  },
  "servers": [
    {
      "url": "https://halalscreener.app",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Screening",
      "description": "Shariah compliance screening for individual and bulk assets."
    },
    {
      "name": "Stocks",
      "description": "Metadata and quote data for supported assets."
    },
    {
      "name": "Keys",
      "description": "API key management (session-auth only; cannot be called with an API key)."
    }
  ],
  "paths": {
    "/api/v1/screen": {
      "get": {
        "tags": [
          "Screening"
        ],
        "summary": "Screen a single asset for Shariah compliance",
        "description": "Returns the AAOIFI verdict, score, grade, and underlying financial ratios for the given symbol. Free keys receive the verdict, score and grade only; `financials`, `purificationPct`, `verdictChangedAt` and `fiscalPeriod` are listed under `withheld` instead of being returned. Pro, Premium, Business and Enterprise keys receive the full object.",
        "operationId": "screenAsset",
        "parameters": [
          {
            "name": "symbol",
            "in": "query",
            "required": true,
            "description": "Ticker symbol (case-insensitive). Examples: `AAPL`, `MSFT`, `BTC`, `SPY`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 12
            },
            "example": "AAPL"
          }
        ],
        "responses": {
          "200": {
            "description": "Screening result",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer",
                  "description": "Unix timestamp (seconds)."
                }
              },
              "X-Quota-Scope": {
                "$ref": "#/components/headers/X-Quota-Scope"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/X-Quota-Limit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/X-Quota-Remaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/X-Plan"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreeningResult"
                },
                "example": {
                  "symbol": "AAPL",
                  "name": "Apple Inc.",
                  "status": "halal",
                  "score": 87,
                  "grade": "A",
                  "gradeLabel": "Very Good",
                  "financials": {
                    "debtToMarketCap": 0.12,
                    "depositsToMarketCap": 0.08,
                    "prohibitedIncomePct": 0.002,
                    "marketCap": 2800000000000
                  },
                  "screenedAt": "2026-04-18T00:00:00.000Z",
                  "url": "https://halalscreener.app/en/stock/AAPL"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/stocks": {
      "get": {
        "tags": [
          "Stocks"
        ],
        "summary": "List supported assets",
        "description": "Paginated list of assets HalalScreener has screened. Use query filters to narrow by status, sector, or asset type.",
        "operationId": "listStocks",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "halal",
                "haram",
                "doubtful"
              ]
            }
          },
          {
            "name": "assetType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "stock",
                "etf",
                "crypto"
              ]
            }
          },
          {
            "name": "sector",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of assets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AssetSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Quota-Scope": {
                "$ref": "#/components/headers/X-Quota-Scope"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/X-Quota-Limit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/X-Quota-Remaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/X-Plan"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/PlanUpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/stock/{symbol}": {
      "get": {
        "tags": [
          "Stocks"
        ],
        "summary": "Get asset detail",
        "description": "Full asset detail including compliance verdict, ratios, and metadata. Free keys receive the verdict, score and grade only; `financials`, `purificationPct`, `verdictChangedAt` and `fiscalPeriod` are listed under `withheld` instead of being returned. Pro, Premium, Business and Enterprise keys receive the full object.",
        "operationId": "getStock",
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Asset detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetDetail"
                }
              }
            },
            "headers": {
              "X-Quota-Scope": {
                "$ref": "#/components/headers/X-Quota-Scope"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/X-Quota-Limit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/X-Quota-Remaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/X-Plan"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/keys": {
      "get": {
        "tags": [
          "Keys"
        ],
        "summary": "List your API keys",
        "description": "Session-auth only. Returns key metadata (prefix, created_at, last_used_at), never the full plaintext key.",
        "operationId": "listApiKeys",
        "responses": {
          "200": {
            "description": "Key list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKeyMetadata"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Keys"
        ],
        "summary": "Create a new API key",
        "description": "Session-auth only. Returns the full key exactly once. Store it securely. Max 3 active keys per user.",
        "operationId": "createApiKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "Full API key. Store it now; it cannot be retrieved again."
                    },
                    "prefix": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "delete": {
        "tags": [
          "Keys"
        ],
        "summary": "Revoke an API key",
        "operationId": "revokeApiKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "prefix": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hs_live_<prefix>_<random>"
      }
    },
    "schemas": {
      "ScreeningResult": {
        "type": "object",
        "required": [
          "symbol",
          "status"
        ],
        "properties": {
          "symbol": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "halal",
              "haram",
              "doubtful"
            ]
          },
          "score": {
            "type": "number",
            "description": "0-100."
          },
          "grade": {
            "type": "string",
            "enum": [
              "A+",
              "A",
              "B+",
              "B",
              "C+",
              "C",
              "D",
              "F"
            ]
          },
          "gradeLabel": {
            "type": "string"
          },
          "financials": {
            "$ref": "#/components/schemas/FinancialRatios"
          },
          "screenedAt": {
            "type": "string",
            "format": "date-time"
          },
          "fiscalPeriod": {
            "type": "string"
          },
          "dataSource": {
            "type": "string",
            "examples": [
              "fmp",
              "edgar"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "withheld": {
            "type": "object",
            "description": "Present on Free keys only: the fields a paid key would have returned, and why.",
            "properties": {
              "fields": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "reason": {
                "type": "string"
              },
              "upgrade": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "FinancialRatios": {
        "type": "object",
        "properties": {
          "debtToMarketCap": {
            "type": "number",
            "description": "Must be < 0.30 for halal under AAOIFI 21."
          },
          "depositsToMarketCap": {
            "type": "number",
            "description": "Interest-bearing deposits. Must be < 0.30."
          },
          "prohibitedIncomePct": {
            "type": "number",
            "description": "Must be < 0.05."
          },
          "marketCap": {
            "type": "number"
          }
        }
      },
      "AssetSummary": {
        "type": "object",
        "properties": {
          "symbol": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "halal",
              "haram",
              "doubtful"
            ]
          },
          "assetType": {
            "type": "string",
            "enum": [
              "stock",
              "etf",
              "crypto"
            ]
          },
          "sector": {
            "type": "string"
          },
          "exchange": {
            "type": "string"
          }
        }
      },
      "AssetDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AssetSummary"
          },
          {
            "$ref": "#/components/schemas/ScreeningResult"
          }
        ]
      },
      "ApiKeyMetadata": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "keyPrefix": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Either the per-minute rate limit for the plan (Retry-After in seconds) or a daily/monthly allowance that is spent (X-Quota-Scope says which; X-Quota-Reset is when it resets).",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          },
          "X-Quota-Scope": {
            "$ref": "#/components/headers/X-Quota-Scope"
          },
          "X-Quota-Reset": {
            "$ref": "#/components/headers/X-Quota-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The Free allowance (3 calls in all) is spent. It never resets; this is a preview of the API, not a plan. Upgrade for a daily or monthly allowance.",
        "headers": {
          "X-Quota-Scope": {
            "$ref": "#/components/headers/X-Quota-Scope"
          },
          "X-Quota-Limit": {
            "$ref": "#/components/headers/X-Quota-Limit"
          },
          "X-Quota-Remaining": {
            "$ref": "#/components/headers/X-Quota-Remaining"
          }
        },
        "content": {
          "application/json": {
            "example": {
              "error": "quota_exceeded",
              "scope": "lifetime",
              "plan": "Free",
              "limit": 3,
              "used": 4,
              "message": "The Free API allowance is 3 calls total, and this account has used all of them.",
              "upgrade": "https://halalscreener.app/en/pricing",
              "docs": "https://halalscreener.app/en/developers"
            }
          }
        }
      },
      "PlanUpgradeRequired": {
        "description": "The endpoint is not included in the key's plan. Free keys get single-symbol verdict lookups only; catalog listing (/v1/stocks, /v1/meta) and bulk export are paid or partner surfaces. The call is not counted against the allowance.",
        "content": {
          "application/json": {
            "example": {
              "error": "plan_upgrade_required",
              "plan": "Free",
              "surface": "Catalog listing (/v1/stocks)",
              "message": "Catalog listing (/v1/stocks) is not included in the Free API allowance.",
              "upgrade": "https://halalscreener.app/en/pricing",
              "docs": "https://halalscreener.app/en/developers"
            }
          }
        }
      }
    },
    "headers": {
      "X-Quota-Scope": {
        "description": "Which allowance applies to this key: lifetime (Free), daily (Pro, Premium) or monthly (Business, Enterprise). Absent on partner keys.",
        "schema": {
          "type": "string",
          "enum": [
            "lifetime",
            "daily",
            "monthly"
          ]
        }
      },
      "X-Quota-Limit": {
        "description": "The allowance for that scope.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Quota-Remaining": {
        "description": "Calls left in that scope after this one.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Quota-Reset": {
        "description": "Unix timestamp (seconds) when a daily or monthly allowance resets. Lifetime allowances never reset.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Plan": {
        "description": "The plan the key rides: Free, Pro, Premium, Business or Enterprise.",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}
