---
title: "Batch Lstar residual returns (up to 100 tickers)"
method: POST
path: "/batch/lstar"
tags: ["Risk Metrics"]
---

# Batch Lstar residual returns (up to 100 tickers)

`POST /batch/lstar`

## Request

- Base URL: `https://riskmodels.app/api`
- URL: `https://riskmodels.app/api/batch/lstar`
- Auth: HTTP bearer

Fetch Lstar-dispatched daily residual return and recommended hedge level (L1/L2/L3) for up to 100 tickers in one call. Uses the same level selection as `GET /lstar`: the canonical ERM3 `lstar_level` when `threshold` is omitted, the legacy marginal-ER rule when a threshold is passed. Response is a `results` map keyed by upper-case ticker; per-ticker failures use `status: error` or `not_found` without failing the whole batch.
**Billing:** $0.015/ticker + $0.0075/extra year, minimum $0.03/call.
**Parquet/CSV:** Long-format table with columns `ticker`, `date`, `lstar`, `residual_return`, hedge ratios, and raw ER inputs. Only successful tickers with non-empty histories are included.

## Request body

- object
  - `tickers` string[], required
  - `market_factor_etf` string
  - `years` integer
  - `threshold` number
  - `axis` 'industry': Cascade axis — `industry` only. DEPRECATED value `style` was removed in v4 and now returns 400 — style is a diagnostic block served by `POST /v4/decompose`; the industry cascade is the only hedge axis.
  - `format` 'json' | 'parquet' | 'csv'

## Response `200`

Batch Lstar results as JSON or long-format Parquet/CSV.

- BatchLstarResponse
  - `results` object, required
  - `summary` object, required
    - `total` integer, required
    - `success` integer, required
    - `errors` integer, required
    - `not_found` integer, required
  - `years` integer, required
  - `threshold_used` number, required
  - `market_factor_etf` string, required
  - `axis` 'industry', required: Always `industry` — the style axis was removed in v4.
  - `_agent` object, required
    - `cost_usd` number
    - `request_id` string
  - `_metadata` RiskMetadata: Lineage and provenance metadata for all data responses. Included in response body as _metadata and as X-Risk-* headers.
    - `model_version` string: ERM3 model version (e.g. ERM3-L3-v30).
    - `data_as_of` string, date: Latest trading date reflected in the response (model outputs / latest snapshot).
    - `factor_set_id` string: Factor set identifier.
    - `universe_size` integer: Number of stocks in the universe.
    - `wiki_uri` string, uri: Methodology documentation URL.
    - `factors` string[]: L3 factor ETF tickers (market + 11 sectors).
    - `data_source` 'zarr': Present on history-style JSON responses. Always `"zarr"` as of the pure-Zarr SSOT cutover: all historical time series (daily metrics, hedge weights, returns decomposition, rankings) are served from consolidated Zarr stores on GCS. The Supabase `security_history` table has been removed; the legacy `"supabase"` value is retained in clients only for backwards compatibility.
    - `range` string[]: Optional inclusive ISO date bounds of the returned history window when `data_source` is set.

## Other responses

- `400`: Invalid request.
- `401`: Unauthorized.
- `402`: Insufficient balance.
- `429`: Rate limit exceeded.

---

[API](https://skmtc.dev/riskmodels/apis/riskmodels-api.md) · [All operations](https://skmtc.dev/riskmodels/apis/riskmodels-api/llms.txt) · [OpenAPI document](https://skmtc.dev/riskmodels/apis/riskmodels-api/revisions/1a034cad408a?raw)
