# Metrics API

> Time series social metrics for stocks and entities: mentions, sentiment, SentiSense Score, and social dominance, with distributions and baselines.

Base URL: `https://app.sentisense.ai/api/v2/metrics`
Tier: Free (API key required; requests consume monthly quota)

All API access requires an API key via the `X-SentiSense-API-Key` header. Keys look like `ss_live_...`.

> SDKs: [Python](https://github.com/SentiSenseApp/sentisense#metrics-v2) · [Node](https://github.com/SentiSenseApp/sentisense-node#entity-metrics)

## Overview

The Metrics API serves the time series behind SentiSense's per-stock social analytics: how often a stock or entity is mentioned, how positive or negative the conversation is, and how much of the total conversation it captures. The same data drives the metrics charts in the SentiSense app. For coverage, methodology, and pricing of the [stock sentiment API](/stock-sentiment-api/) as a product, start there and come back here for the call signatures.

**Metric types:**

| Key | What it measures |
|-----|------------------|
| `mentions` | Number of mentions across tracked sources |
| `sentisense` | The SentiSense Score, a signed read on how bullish or bearish the market is on a stock, from news and social sentiment weighted by how actively it's discussed. The recommended metric for a bullish-or-bearish read; each point carries its `bull`, `bear` and `directional` mention counts under `metricValue.properties`. `sentisense_score` is an alias for the same series |
| `sentiment` | Average sentiment of those mentions (-1.0 to 1.0), unweighted by volume |
| `social_dominance` | Share of voice relative to other tracked stocks |
| `sentisense_rating` | The daily SentiSense Rating score, 0 to 100, which is the number the letter is banded from and not the percentile. Stocks only, one point per day the stock was rated. The letter and the per-dimension breakdown ride under `metricValue.properties`; see the [SentiSense Rating API](/docs/api/sentisense-rating/) |
| `app_review_count` | Number of new Apple App Store reviews published that day for the product's app. Present only on product entities that have an app |
| `app_rating` | Average star rating, 1.0 to 5.0, of that day's new App Store reviews. Present only on product entities that have an app |

`mentions` counts news, social, video and forum mentions. App Store reviews are served separately as `app_review_count` and `app_rating`; their tone still counts toward `sentiment` and `sentisense`, and the App Store slice still appears in the source distribution. History was restated on this definition, so a long window reads consistently.

The `sentiment` metric here is a per-entity time series. For the cross-sectional market-wide cut of the same tone measure, use `/api/v1/sentiment/sectors` (the 11 GICS sectors versus the market's own tone, since news tone skews positive as a genre) and `/api/v1/sentiment/breadth` (the bullish/neutral/bearish share of the roughly 1,000 covered stocks) in the [Market Mood API](/docs/api/market-mood/).

**Entity resolution:** the `{entityId}` path segment accepts a stock ticker (e.g. `AAPL`) or an entity `urlSlug` (e.g. `Nancy-Pelosi`), so you can query people, products, and organizations the same way you query stocks. Both are case-insensitive, and a ticker-shaped identifier always addresses the listed company (never a similarly named product). Discover handles with `GET /api/v1/kb/entities/search?q=` (see the [Entities API](/docs/api/entities/)) or `GET /api/v1/stocks/{ticker}/entities`. Internal KB ids, including the dashed `kb-person-67` form, are not accepted. An identifier that resolves to no known entity returns `404 entity_not_found` with up to three search-based `suggestions`.

**Time windows:** `startTime` and `endTime` are epoch milliseconds. When omitted, the window defaults to the last 7 days.

**Access:** every metric type is available on the Free tier, with no PRO gating. An API key is required on every call, and each request counts against your monthly quota (Free: 1,000 requests/month; PRO: no monthly cap) as well as your per-minute rate limit. Anonymous calls return `401 api_key_required`.

**Versioning:** the Metrics API lives at `/api/v2/metrics` (v2, not the `/api/v1/...` prefix used by most other endpoints). The legacy `/api/v1/entity-metrics/*` family is retired and returns `410 endpoint_retired`; use this API instead.

Working in Python? Five runnable scripts: [Python stock sentiment examples](/blog/python-stock-sentiment-examples/).

---

## GET /entity/{entityId}/metric/{metricType}

Returns time series data points for one metric on one entity over a time window.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker (e.g. `AAPL`) or entity `urlSlug` (e.g. `Nancy-Pelosi`) |
| `metricType` | path | Yes | - | `mentions`, `sentiment`, `sentisense` (alias `sentisense_score`), `social_dominance`, `sentisense_rating`, `app_review_count`, or `app_rating`. Keys are case-insensitive |
| `startTime` | long | No | 7 days ago | Window start (epoch milliseconds) |
| `endTime` | long | No | now | Window end (epoch milliseconds) |
| `maxDataPoints` | int | No | - | Return at most N of the window's points, unmerged, in ascending order, evenly spaced back from the latest point, which is always included; it caps the points and does not set the window |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/metric/sentisense"
```

**Response:** array of data points ordered ascending by `timestamp` (empty array when the window has no data). Each point:

| Field | Type | Description |
|-------|------|-------------|
| `timestamp` | long | Point timestamp (epoch milliseconds) |
| `metricType` | string | The metric type of the point, as an uppercase enum name. It is the canonical name, so a `sentisense` request answers with `SENTISENSE_SCORE`. Feeding it back as `metricType` on a later request resolves to the same series |
| `value` | number | The flattened scalar reading: the polarity for `sentiment`, the count for `mentions`. Read this instead of walking `metricValue`. Omitted when the point has no reading (treat as no reading, not `0`) |
| `metricValue` | object | The full reading including the stats block. Count metrics (`mentions`, `app_review_count`) carry the scalar at `metricValue.value`; value metrics (`sentiment`, `sentisense`, `social_dominance`, `sentisense_rating`, `app_rating`) nest it one level deeper at `metricValue.value.value`, alongside aggregation metadata such as `minValue`, `maxValue`, and `dataPointCount`, and a per-metric `properties` map where one exists |

**`sentisense` point properties** (under `metricValue.properties`):

| Field | Type | Description |
|-------|------|-------------|
| `bull` | number | Mentions that day our models scored bullish for the entity |
| `bear` | number | Mentions that day our models scored bearish for the entity |
| `directional` | number | `bull` plus `bear`: the mentions that carried a direction, and the sample size behind that day's Score |

Read direction from `bull` against `bear`; `mentions` counts every mention whatever its tone, so a spike in it says nothing about direction. Points are daily, one per New York calendar day, and the last point is the current day so far.

**Deriving the current value and its change:** because points are ordered ascending by `timestamp`, the current reading is the last point's `value` and the change is the last point's `value` minus the prior point's (or minus the first point's for the whole window). A window with 0 or 1 point has no derivable trend: widen `startTime` rather than reporting a change.

Returns `400` for an unknown metric type.

---

## GET /entity/{entityId}/distribution/{metricType}

Returns how a metric breaks down across a dimension over a time window. What each number means depends on the metric, and `valueType` in the response names it:

- **Count metrics** (`mentions`, `creators`, `app_review_count`): each dimension value's percentage share of the metric's total over the window, summing to about 100. `valueType` is `SHARE_PERCENT`. Example: what share of a stock's mentions came from each source.
- **Value metrics** (`sentiment`): each dimension value's mean reading over the window, on the metric's own scale. For `sentiment` by `source` that is each source's average polarity in [-1, 1], the same numbers [GET /entity/{entityId}/metric/{metricType}/mean-by/{dimension}](#get-metric-mean-by) returns for the same entity and window. `valueType` is `MEAN`.

`sentisense` returns an empty map with `valueType` `MEAN`: the SentiSense Score is computed over an entity's whole conversation and has no per-source split. For where that conversation came from, ask for `distribution/mentions`. A metric with no per-dimension readings, such as `sentisense_rating`, `social_dominance` or `app_rating`, also returns an empty map.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker or entity `urlSlug` |
| `metricType` | path | Yes | - | Metric type key, e.g. `mentions` for shares or `sentiment` for per-source means |
| `dimension` | string | Yes | - | Dimension to break down by (e.g. `source`) |
| `startTime` | long | No | 7 days ago | Window start (epoch milliseconds) |
| `endTime` | long | No | now | Window end (epoch milliseconds) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/distribution/mentions?dimension=source"
```

```json
{
  "metricType": "mentions",
  "dimension": "source",
  "distribution": { "News": 74.88, "Reddit": 12.61, "X": 5.86, "YouTube": 5.54, "Hacker News": 0.69, "Substack": 0.42 },
  "valueType": "SHARE_PERCENT"
}
```

The same call for `sentiment` returns per-source means instead:

```json
{
  "metricType": "sentiment",
  "dimension": "source",
  "distribution": { "Hacker News": 0.458, "Substack": 0.438, "News": 0.383, "X": 0.299, "YouTube": 0.251, "Reddit": 0.07 },
  "valueType": "MEAN"
}
```

Both examples leave out `timestamp`, `dimensionDisplayName` and `granularity`; the full field list is below.

**Response:**

| Field | Type | Description |
|-------|------|-------------|
| `metricType` | string | Metric type queried |
| `timestamp` | long | When the distribution was computed (epoch milliseconds) |
| `dimension` | string | Dimension queried |
| `dimensionDisplayName` | string | Human-readable dimension label |
| `distribution` | object | Map of dimension value to a number whose meaning `valueType` gives. Empty map when there is no data |
| `granularity` | string | Granularity of the underlying data |
| `valueType` | string | `SHARE_PERCENT` for a count metric: each value is a percentage share of the metric's total, summing to about 100. `MEAN` for a value metric: each value is the mean reading on the metric's own scale, never a share |

Returns `400 invalid_parameter` for a dimension outside `source`, `publisher`, `type`, and `400` for an unknown metric type or `startTime` after `endTime`.

---

## GET /entity/{entityId}/metric/{metricType}/mean-by/{dimension}

Returns the mean value of a metric per dimension value over a time window. For `sentiment` grouped by `source` this is the per-source sentiment polarity, the same numbers `distribution/sentiment` returns in its `distribution` map for the same entity and window. For each source's share of the conversation, use `distribution/mentions`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker or entity `urlSlug` |
| `metricType` | path | Yes | - | Metric type key (e.g. `sentiment`) |
| `dimension` | path | Yes | - | Dimension to group by (e.g. `source`) |
| `startTime` | long | No | 7 days ago | Window start (epoch milliseconds) |
| `endTime` | long | No | now | Window end (epoch milliseconds) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/metric/sentiment/mean-by/source"
```

**Response:** flat map of dimension value to mean, e.g. `{ "NEWS": 0.42, "REDDIT": -0.05 }`. For sentiment by source, each value is the average of that source's daily mean readings inside the window; a window with no data returns `{}`. Returns `400 invalid_parameter` for a dimension outside `source`, `publisher`, `type`, and `400` for an unknown metric type or when `startTime` is after `endTime`.

---

## GET /entity/{entityId}/metric/{metricType}/slices

Lists the slice dimensions (and their values) available for a metric on an entity. Use this to discover what you can pass to the distribution and mean-by endpoints before querying them.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker or entity `urlSlug` |
| `metricType` | path | Yes | - | Metric type key |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/metric/mentions/slices"
```

**Response:** array of slice descriptors (empty array when the metric has no slices):

| Field | Type | Description |
|-------|------|-------------|
| `metricType` | string | Metric type the slice applies to |
| `dimension` | string | Dimension key (e.g. `source`) |
| `values` | array | The dimension values present in the data |
| `displayName` | string | Human-readable dimension label |

---

## GET /entity/{entityId}/baselines/{metricType}

Returns weekly baseline series for a metric: a historical baseline (the entity against its own past) and a peer baseline (the entity against comparable entities). Read the units note below before comparing a baseline with a daily reading.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker or entity `urlSlug` |
| `metricType` | path | Yes | - | Metric type key |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/baselines/sentiment"
```

**Response:**

| Field | Type | Description |
|-------|------|-------------|
| `metricType` | string | Metric type queried |
| `availableBaselines` | array | `["HISTORICAL_WEEKLY", "PEER_WEEKLY"]` |
| `defaultBaseline` | string | `"HISTORICAL_WEEKLY"` |
| `showByDefault` | boolean | UI hint |
| `baselines` | object | Map of baseline type to baseline object (a type is absent when no baseline has been computed yet) |

**Baseline object:**

| Field | Type | Description |
|-------|------|-------------|
| `metricType` | string | Metric the baseline was computed for |
| `baselineType` | string | `HISTORICAL_WEEKLY` or `PEER_WEEKLY` |
| `peerGroup` | string | Peer group used for the `PEER_WEEKLY` baseline |
| `weeklyData` | array | Weekly baseline points (see below) |
| `lastUpdated` | long | When the baseline was last recomputed |

**Weekly baseline point:**

| Field | Type | Description |
|-------|------|-------------|
| `weekOf` | string | ISO week label (e.g. `"2026-W22"`) |
| `averageValue` | double | Weekly average value, in the row unit described below, not the daily unit |
| `dataPoints` | int | Number of stored rows contributing to the average (rows, not days) |
| `weekStartTimestamp` | long | Week start (epoch milliseconds) |
| `dailyData` | array | Per-day breakdown within the week |

**Units: not the daily series.** `averageValue` and each `dailyData[].baselineValue` are plain means over the metric rows computed for the entity in that week or on that weekday, breakdown rows (per source and so on) included, so they are not in the unit of `GET /entity/{entityId}/metric/{metricType}`. For `mentions` they typically sit several to more than ten times below the daily counts, so comparing a day's count against `averageValue` reads an ordinary day as a spike. To judge whether a day is unusual, compare it with the metric's own trailing daily values, such as the 30-day median from `/metric/mentions`.

---

## Errors

| Status | Code | Description |
|--------|------|-------------|
| 400 | - | Unknown metric type, empty dimension, or `startTime` after `endTime` |
| 404 | entity_not_found | The `{entityId}` matches no known entity. Carries up to three `suggestions` (`name`, `urlSlug`, `ticker`). A known entity with no readings in the window is an empty `200`, not this |
| 401 | api_key_required | No API key on the call |
| 429 | rate_limit_exceeded | Per-minute rate limit exceeded (Free: 30/min, PRO: 300/min) |
| 429 | quota_exceeded | Monthly request quota exhausted (Free: 1,000/month; PRO has no monthly cap) |
| 410 | endpoint_retired | Returned by the legacy `/api/v1/entity-metrics/*` paths; use `/api/v2/metrics/...` |
