---
title: "Get dilution risk assessment"
method: GET
path: "/dilution/{ticker}/risk"
tags: ["Dilution"]
---

# Get dilution risk assessment

`GET /dilution/{ticker}/risk`

## Request

- Base URL: `https://api.signal8.ai/api/v1/public`
- URL: `https://api.signal8.ai/api/v1/public/dilution/{ticker}/risk`
- Auth: API key in header `X-Api-Key`

A 0-100 dilution pressure score, four qualitative risk levels, and the
per-component breakdown behind them.

🔴 `score` IS NULLABLE, AND SO ARE ALL FIVE LEVELS — and since
2026-10-06 a published value may be a FLOOR or a PARTIAL score, with the
hedge carried in a field beside it. Read the hedge field every time:

- **No dilution snapshot for the ticker** — `score` is null with
  `scoreWithheldReason: "no_dilution_snapshot"`, and `overall`,
  `offeringAbility`, `overheadSupply`, `historical`, `cashNeed`,
  `hasActiveShelf` and `hasActiveAtm` are ALL null with
  `levelsWithheldReason: "no_dilution_snapshot"`. A null level is NOT
  `low`. Defaulting it to a level on the client re-creates exactly the
  defect this withholding exists to prevent.
- **A live instrument whose dilution could not be quantified (one
  family)** — `overall` and `overheadSupply` are PUBLISHED as LOWER
  BOUNDS with `levelsAreLowerBound: true` and
  `levelsLowerBoundReason: "unquantified_live_instruments"`: an unsized
  row contributes zero to the supply sum, so the true level can only be
  at or above the one shown. Render it as "at least MEDIUM", never as a
  bare MEDIUM. (`levelsAreLowerBound` is false when the floor is already
  `high` — the top of the scale, so it is exact.) The holed score
  component is dropped from the denominator (see below). The supply
  figures in `details` (`dilutiveSharesPct`, `totalDilutiveShares`, …)
  stay null: a holed sum has no hedge field of its own here.
- **A partial score** — when a component could not be measured (an
  unsized instrument family, or an absent INPUT such as no stock price
  or no market cap) it is DROPPED FROM THE DENOMINATOR: `score` is out of
  `scoreMaxMeasured` (e.g. `58` of `80`), never out of 100, and
  `scoreUnmeasuredComponents` names each excluded component with its
  `reason`. Never rescale to /100, and never call the score a floor —
  filling a gap can move it DOWN. `scoreMaxMeasured` is null on an
  ordinary /100 score.
- **Too little measured** — below 70 measured points the score is null
  with `scoreWithheldReason: "unmeasured_score_components"`, or
  `"unquantified_live_instruments"` when the instrument holes alone take
  it there (e.g. warrants AND convertibles both unsized); in that second
  case `overall` and `overheadSupply` are withheld too
  (`levelsWithheldReason: "unquantified_live_instruments"`).

`offeringAbility` is a CAPABILITY profile, the score is PRESSURE TODAY:
a live ATM / equity line whose drawable capacity is a MEASURED $0 (for
example clamped to $0 by the I.B.6 one-third-of-float cap, with no row of
unknown capacity) reads `medium` — the facility exists — while its score
component prices the $0. Any row of unknown capacity keeps `high`.

Until 2026-10-06 this endpoint withheld everything short of a full
measurement; it now matches the Signal8 web app, which shows the same
values as `≥ MEDIUM` and `58 / 80`. `scoreBreakdown` lists only the
MEASURED components and is `[]` whenever `score` is null, because a
"0/20 — No stock price data" row beside a withheld score preserves the
claim the null removes.

`details.grossDilutiveSupplyPct` and `details.inTheMoneySupplyPct` are
DIFFERENT QUANTITIES and can differ by an order of magnitude on an
out-of-the-money book. The in-the-money figure is null when
`inTheMoneySupplyWithheldReason` is set; a real measured 0 travels as 0
with a null reason.

**Credit cost: 5**

## Path parameters

- `ticker` string, required

## Response `200`

Risk assessment. Any field may be null; a null is NOT MEASURED.

- object

## Other responses

- `400`: Invalid request parameters
- `401`: Missing or invalid API key
- `402`: Not enough credits for this request
- `403`: The API key's owner does not hold the per-user add-on this endpoint is sold under. 🔴 This is NOT a tier problem — a Pro subscription does not confer an add-on. Route the user to add-on purchase (`addonType` names which one), not to a plan upgrade. The distinct `ADDON_REQUIRED` code exists so the two flows can be told apart; treating it as `TIER_INSUFFICIENT` sends a paying customer to a page that cannot sell them what they were denied.
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/signal8/apis/signal8-public-api.md) · [All operations](https://skmtc.dev/signal8/apis/signal8-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/signal8/apis/signal8-public-api/revisions/5a18ae8d6d58?raw)
