---
title: "Exposure history feed — month-end model history for a list of names (join locally)"
method: POST
path: "/portfolio/exposure/history"
tags: ["Risk Metrics"]
---

# Exposure history feed — month-end model history for a list of names (join locally)

`POST /portfolio/exposure/history`

## Request

- Base URL: `https://riskmodels.app/api`
- URL: `https://riskmodels.app/api/portfolio/exposure/history`
- Auth: HTTP bearer

Model data feed for long/short books. Send a list of tickers — no position values — and receive signed URLs (1 hour) to two Parquet files: each name's month-end ERM3 history (`names`: hedge ratios at L1/L2/L3, explained-risk and residual shares, `stock_var`, `lstar_level`, `l1_mkt_beta` = −`l1_mkt_hr`, sector and subsector ETF) and the ETF covariance over the 252 trading days ending each month-end (`cov`, upper triangle). Holdings never leave the client: the Python SDK (`client.exposure_history(...).exposure(holdings_by_date)`) joins the feed with dated holdings locally and returns the same quantities as `POST /portfolio/exposure` at each month-end.

Each row is the last trading day of its month; the current month uses its latest model day.

**History start.** Model history is served from the first date whose full estimation window is covered: hedge ratios and `l1_mkt_beta` from 2010-12-08, explained-risk and residual shares and `stock_var` from 2011-12-07, `lstar_level` from 2013-01-01. Rows start at the first month-end on or after 2010-12-08 (2010-12-31); each column is null before its own start date and nothing earlier is served. That is the default `start`; an earlier `start` is moved up to it. `history_available_from` gives each column's first date, and `warnings` has one `before_served_history` entry per start date later than the requested `start`. The same applies to `/portfolio/exposure/history/daily`. Names with no model rows in range are dropped as `no_data_in_range`; ETFs stay in the covariance universe. The ETF-to-sector mapping is today's registry, not point-in-time. Derived data only: no prices, market caps or return series.

**Billing:** per successful call, by names delivered (stocks with rows plus ETFs in the covariance): **$1.25** up to 25, **$5.00** above 25. A request with nothing to deliver returns 422 and is not charged. Identical requests on the same panel build reuse the stored files.

## Request body

- object
  - `tickers` string[], required: Stocks and ETFs. No values.
  - `start` string, date: First month-end to include. Default and earliest 2010-12-08 (an earlier start is moved up to it).
  - `end` string, date: Last month-end to include. Default latest.

## Response `200`

Signed Parquet URLs plus coverage.

- object
  - `as_of` object: first_teo, last_teo, months, frequency, panel_built_utc, source_last_teo.
  - `files` object: names and cov — each with url, format, rows, columns; expires_in_seconds; cached.
  - `names_delivered` integer
  - `etfs_in_covariance` string[]
  - `dropped` object[]
    - `ticker` string
    - `reason` 'symbol_not_found' | 'no_data_in_range' | 'etf_without_covariance'
  - `history_available_from` object: First date each column is served (column → YYYY-MM-DD); null before it.
  - `warnings` object[]: One entry per column start date later than the requested `start`; empty otherwise.
    - `code` 'before_served_history'
    - `available_from` string, date
    - `columns` string[]
    - `message` string

## Other responses

- `400`: Invalid request (empty or more than 1000 tickers, bad date).
- `422`: Nothing to deliver. Not charged.
- `503`: The month-end panel is not available.

---

[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)
