{
  "openapi": "3.1.0",
  "info": {
    "title": "FaithScreener Partner API",
    "version": "1.0.0",
    "description": "Multi-tradition faith-based stock screening. Screen any of 124,000+ tickers across 46 markets against 10 compliance frameworks (five Shariah methodologies, Christian BRI, Catholic USCCB, Jewish Halakhic, LDS, and the FaithScreener Consensus Score), with point-in-time verdicts from a compliance-history archive reaching back to 2009. This hand-curated document describes the partner surface only. Authentication: send your API key in the X-API-Key header. Partner keys may be scoped with allowed_markets, allowed_endpoints (tags: screen, screen/history, screen/batch, screen/etf, screen/compare), and allowed_frameworks; responses are automatically filtered to the key's framework grant. Quotas are charged in units: a screen is 1 unit, a batch charges 1 unit per ticker, an as_of read is 2 units by default. Point-in-time note: snapshots dated before 2026-07-04 come from earlier engine revisions with known verdict defects and always carry era \"legacy\" plus a data_quality warning; treat them as indicative rather than authoritative.",
    "contact": {
      "name": "FaithScreener",
      "url": "https://faithscreener.com/pricing"
    }
  },
  "servers": [
    { "url": "https://faithscreener.com" }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "paths": {
    "/api/screen/{market}/{ticker}": {
      "get": {
        "summary": "Screen a single stock (current or point-in-time verdict)",
        "description": "Screens a stock against all 10 frameworks. Serves a cached result when fresh (stale-while-revalidate). With as_of=YYYY-MM-DD, returns the point-in-time verdict from compliance history instead (endpoint tag screen/history, 2 units). The framework parameter is a response filter only - it never changes what is computed or cached.",
        "operationId": "screenStock",
        "parameters": [
          { "$ref": "#/components/parameters/Market" },
          { "$ref": "#/components/parameters/Ticker" },
          { "$ref": "#/components/parameters/Framework" },
          {
            "name": "as_of",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD. Point-in-time verdict from compliance history (2009-01-01 to today). Malformed, pre-2009, or future dates return 400; no snapshot at or before the date returns 404.",
            "schema": { "type": "string", "format": "date", "example": "2024-06-01" }
          },
          {
            "name": "force_refresh",
            "in": "query",
            "required": false,
            "description": "true bypasses the cache and re-screens from source data. Ignored on as_of reads.",
            "schema": { "type": "boolean", "default": false }
          }
        ],
        "responses": {
          "200": {
            "description": "Current screen result, or a point-in-time snapshot when as_of is set.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/ScreenResult" },
                    { "$ref": "#/components/schemas/AsOfResult" }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/api/screen/batch": {
      "post": {
        "summary": "Bulk screen up to 100 (market, ticker) pairs",
        "description": "Each ticker charges 1 unit against the key's quota (endpoint tag screen/batch). The optional frameworks array filters every result's report.",
        "operationId": "screenBatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BatchRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch outcome with per-ticker results and errors.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/api/screen/etf": {
      "post": {
        "summary": "Screen an ETF/fund across its top holdings",
        "description": "Aggregates verdicts across the fund's top holdings (endpoint tag screen/etf, 1 unit). framework_stats and each holding's by_framework map are scoped to the key's framework grant.",
        "operationId": "screenEtf",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EtfRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fund aggregate with per-framework pass/fail stats and per-holding verdicts.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EtfResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/api/screen/compare": {
      "get": {
        "summary": "Compare 2-4 stocks side by side",
        "description": "Screens 2-4 tickers in one call (endpoint tag screen/compare, 1 unit total). Each result's report is scoped to the key's framework grant.",
        "operationId": "compareStocks",
        "parameters": [
          {
            "name": "tickers",
            "in": "query",
            "required": true,
            "description": "Comma-separated tickers, 2 to 4.",
            "schema": { "type": "string", "example": "AAPL,MSFT,GOOG" }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Market slug. Defaults to us.",
            "schema": { "type": "string", "default": "us" }
          }
        ],
        "responses": {
          "200": {
            "description": "Combined results for side-by-side comparison.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CompareResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/api/screen/frameworks": {
      "get": {
        "summary": "List the 10 canonical frameworks (public, uncounted)",
        "description": "Static metadata: framework ids, labels, and traditions. No auth required and never charged against any quota. When a valid X-API-Key header is present, the response also includes key_scope with the key's provisioning.",
        "operationId": "listFrameworks",
        "security": [],
        "responses": {
          "200": {
            "description": "Framework registry, optionally with the caller's key scope.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FrameworksResult" }
              }
            }
          }
        }
      }
    },
    "/api/universe/search": {
      "get": {
        "summary": "Search the 124,000+ ticker universe (public)",
        "description": "Searches by ticker prefix or company name. Public; no quota.",
        "operationId": "searchUniverse",
        "security": [],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query (1-64 chars). Matches ticker prefixes and company names.",
            "schema": { "type": "string", "minLength": 1, "maxLength": 64 }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Restrict results to one market slug.",
            "schema": { "type": "string" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching tickers.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResult" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key, format fsk_live_... . Partner keys are admin-minted with custom quotas and allowed_markets / allowed_endpoints / allowed_frameworks scopes."
      }
    },
    "parameters": {
      "Market": {
        "name": "market",
        "in": "path",
        "required": true,
        "description": "Market slug, e.g. us, uk, japan, malaysia. Use market_key values from GET /api/markets.",
        "schema": { "type": "string", "example": "us" }
      },
      "Ticker": {
        "name": "ticker",
        "in": "path",
        "required": true,
        "description": "Ticker symbol, e.g. AAPL.",
        "schema": { "type": "string", "example": "AAPL" }
      },
      "Framework": {
        "name": "framework",
        "in": "query",
        "required": false,
        "description": "Comma-separated canonical framework ids to return (response filter). Unknown ids return 400 listing the valid ids; ids outside the key's allowed_frameworks return 403 naming the denied ids.",
        "schema": {
          "type": "string",
          "example": "catholic_usccb,christian_bri"
        }
      }
    },
    "schemas": {
      "FrameworkId": {
        "type": "string",
        "enum": [
          "catholic_usccb",
          "christian_bri",
          "faithscreener_consensus",
          "jewish_halakhic",
          "lds_mormon",
          "shariah_aaoifi",
          "shariah_djim",
          "shariah_ftse",
          "shariah_msci",
          "shariah_sp"
        ]
      },
      "FrameworkStatus": {
        "type": "string",
        "enum": ["compliant", "non_compliant", "questionable", "insufficient_data"]
      },
      "FrameworkNode": {
        "type": "object",
        "description": "Per-framework verdict node. Full reports carry reasons and ratio details; history snapshots carry status plus key ratios only.",
        "properties": {
          "status": { "$ref": "#/components/schemas/FrameworkStatus" }
        },
        "additionalProperties": true
      },
      "Report": {
        "type": "object",
        "description": "Compliance report. Framework verdicts are TOP-LEVEL keys named by framework id (e.g. report.catholic_usccb.status). When a framework filter or key scope applies, non-granted nodes are absent and frameworks_returned lists what was kept; madhhab_views is only present when at least one shariah_* framework is returned.",
        "properties": {
          "ticker": { "type": "string" },
          "company_name": { "type": "string" },
          "market": { "type": "string" },
          "sic_code": { "type": ["string", "null"] },
          "sic_description": { "type": ["string", "null"] },
          "financial_data": { "type": ["object", "null"], "additionalProperties": true },
          "incidental_exposure": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "frameworks_returned": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/FrameworkId" },
            "description": "Present only on framework-filtered responses."
          }
        },
        "additionalProperties": true
      },
      "ScreenResult": {
        "type": "object",
        "properties": {
          "ticker": { "type": "string" },
          "market": { "type": "string" },
          "report": { "$ref": "#/components/schemas/Report" },
          "from_cache": { "type": "boolean" },
          "cache_age_days": { "type": "integer" },
          "stale": { "type": "boolean" },
          "rate_limit": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      },
      "DataQuality": {
        "type": "object",
        "description": "Present ONLY when era is \"legacy\" (snapshot predates the 2026-07-04 engine revision).",
        "properties": {
          "warning": { "type": "string" },
          "engine_current_since": { "type": "string", "example": "2026-07-04" }
        }
      },
      "AsOfResult": {
        "type": "object",
        "description": "Point-in-time verdict (as_of reads). Snapshot-shaped: per-framework status plus key ratios - deliberately NOT the full report shape.",
        "required": ["ticker", "market", "as_of", "era", "frameworks", "historical"],
        "properties": {
          "ticker": { "type": "string" },
          "market": { "type": "string" },
          "as_of": { "type": "string", "format": "date", "description": "The date that was asked." },
          "snapshot_date": { "type": ["string", "null"], "format": "date", "description": "Day of the snapshot actually served (latest at or before as_of)." },
          "screened_at": { "type": ["string", "null"], "description": "Raw snapshot timestamp (ISO 8601)." },
          "source": { "type": ["string", "null"], "enum": ["runtime", "historical_backfill", null] },
          "era": {
            "type": "string",
            "enum": ["legacy", "current"],
            "description": "\"current\" iff the snapshot is from 2026-07-04 or later; \"legacy\" rows come from earlier engine revisions with known verdict defects."
          },
          "data_quality": { "$ref": "#/components/schemas/DataQuality" },
          "frameworks": {
            "type": "object",
            "description": "Per-framework snapshot nodes, filtered by the framework parameter and the key's allowed_frameworks.",
            "additionalProperties": { "$ref": "#/components/schemas/FrameworkNode" }
          },
          "historical": { "type": "boolean", "const": true }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": ["tickers"],
        "properties": {
          "tickers": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": ["market", "ticker"],
              "properties": {
                "market": { "type": "string", "example": "us" },
                "ticker": { "type": "string", "example": "AAPL" }
              }
            }
          },
          "frameworks": {
            "type": ["array", "null"],
            "items": { "$ref": "#/components/schemas/FrameworkId" },
            "description": "Optional response filter applied to every result's report."
          },
          "force_refresh": { "type": "boolean", "default": false }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "requested": { "type": "integer" },
          "succeeded": { "type": "integer" },
          "failed": { "type": "integer" },
          "partial": { "type": "boolean" },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/ScreenResult" } },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ticker": { "type": "string" },
                "market": { "type": "string" },
                "error": { "type": "string" }
              }
            }
          },
          "elapsed_ms": { "type": "integer" }
        }
      },
      "EtfRequest": {
        "type": "object",
        "required": ["etf_ticker"],
        "properties": {
          "etf_ticker": { "type": "string", "example": "SPY" },
          "market": { "type": "string", "default": "us" },
          "max_holdings": { "type": "integer", "minimum": 1, "maximum": 50, "default": 15 }
        }
      },
      "EtfResult": {
        "type": "object",
        "properties": {
          "etf_ticker": { "type": "string" },
          "market": { "type": "string" },
          "holdings_screened": { "type": "integer" },
          "total_holdings_attempted": { "type": "integer" },
          "framework_stats": {
            "type": "object",
            "description": "Per-framework pass/fail/unknown/na counts, keyed by framework id (scoped to the key's grant).",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "pass": { "type": "integer" },
                "fail": { "type": "integer" },
                "unknown": { "type": "integer" },
                "na": { "type": "integer" }
              }
            }
          },
          "holdings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ticker": { "type": "string" },
                "name": { "type": "string" },
                "weight": { "type": "number" },
                "overall": { "type": "string" },
                "by_framework": {
                  "type": "object",
                  "additionalProperties": { "$ref": "#/components/schemas/FrameworkStatus" }
                }
              }
            }
          },
          "from_cache": { "type": "boolean" }
        },
        "additionalProperties": true
      },
      "CompareResult": {
        "type": "object",
        "properties": {
          "tickers": { "type": "array", "items": { "type": "string" } },
          "market": { "type": "string" },
          "results": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ScreenResult" },
                {
                  "type": "object",
                  "properties": {
                    "ticker": { "type": "string" },
                    "market": { "type": "string" },
                    "error": { "type": "string" }
                  }
                }
              ]
            }
          },
          "frameworks": { "type": "array", "items": { "$ref": "#/components/schemas/FrameworkId" } }
        }
      },
      "FrameworksResult": {
        "type": "object",
        "properties": {
          "count": { "type": "integer", "const": 10 },
          "frameworks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "$ref": "#/components/schemas/FrameworkId" },
                "label": { "type": "string" },
                "tradition": { "type": "string" }
              }
            }
          },
          "key_scope": {
            "type": "object",
            "description": "Present only when a valid X-API-Key header accompanies the request.",
            "properties": {
              "allowed_frameworks": { "type": ["array", "null"], "items": { "$ref": "#/components/schemas/FrameworkId" } },
              "allowed_markets": { "type": ["array", "null"], "items": { "type": "string" } },
              "allowed_endpoints": { "type": ["array", "null"], "items": { "type": "string" } }
            }
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "query": { "type": "string" },
          "count": { "type": "integer" },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ticker": { "type": "string" },
                "yahoo_symbol": { "type": ["string", "null"] },
                "name": { "type": "string" },
                "market": { "type": "string" },
                "exchange": { "type": ["string", "null"] },
                "country": { "type": ["string", "null"] }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Error envelope. detail is a string for most errors; some 403/429 responses carry a structured object with limits and an upgrade_url.",
        "properties": {
          "detail": {
            "oneOf": [
              { "type": "string" },
              { "type": "object", "additionalProperties": true }
            ]
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid market, ticker, framework id, or as_of date. The detail message names the valid values.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Key not provisioned for this market, endpoint, or framework - or the key owner's tier lapsed.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Ticker not found, no data available, or no compliance history at or before as_of.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "QuotaExceeded": {
        "description": "Quota exhausted - key daily limit or partner monthly quota.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
