# 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_...`.
<!--
  KEEP IN SYNC: the frontmatter `endpoints:` list above must stay aligned with the
  `## GET /...` (or POST/PUT/DELETE) section headings in this file:
    - SAME ORDER: lib/docs.js stamps each ep.id onto the next method-prefixed heading
      in document order. If the orders drift, sidebar clicks scroll to the wrong section.
    - SAME COUNT: bump `endpointCount` and add/remove entries in `endpoints:` whenever
      you add or remove a section here.
    - When you change paths, update the skill source at `skills/src/sentisense/body.md` and run
      `cd skills && npm run deploy:website` to regenerate `public/skill.md`.

  Note: Metrics lives at /api/v2/metrics (v2, not v1), like Market Mood.
-->

## 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.

**Metric types:**

| Key | What it measures |
|-----|------------------|
| `mentions` | Number of mentions across tracked sources |
| `sentiment` | Average sentiment of those mentions (-1.0 to 1.0) |
| `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 |
| `social_dominance` | Share of voice relative to other tracked stocks |

**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`. The legacy URL-safe id form (`kb-person-67`) keeps working. 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.

---

## 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`, or `social_dominance` |
| `startTime` | long | No | 7 days ago | Window start (epoch milliseconds) |
| `endTime` | long | No | now | Window end (epoch milliseconds) |
| `maxDataPoints` | int | No | - | Downsampling hint for long windows |

**Example Request:**

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

**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 |
| `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`) carry the scalar at `metricValue.value`; value metrics (`sentiment`, `sentisense`, `social_dominance`) nest it one level deeper at `metricValue.value.value`, alongside aggregation metadata such as `minValue`, `maxValue`, and `dataPointCount` |

**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: for example, what share of a stock's mentions came from each source.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Stock ticker or entity `urlSlug` |
| `metricType` | path | Yes | - | Metric type key (e.g. `mentions`) |
| `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"
```

**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 its share of the metric's data points over the window (empty map when no data) |
| `granularity` | string | Granularity of the underlying data |

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. The companion to the distribution endpoint: distribution answers "how much of the conversation came from each source", mean-by answers "what was the average reading per source". For `sentiment` grouped by `source` this is the per-source sentiment polarity.

**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 baseline series for a metric so you can tell whether the current reading is unusual: a historical baseline (the entity against its own past) and a peer baseline (the entity against comparable entities). This is the endpoint behind anomaly framing like "mentions are running above their weekly baseline".

**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 |
| `dataPoints` | int | Number of days contributing to the average |
| `weekStartTimestamp` | long | Week start (epoch milliseconds) |
| `dailyData` | array | Per-day breakdown within the week |

---

## Errors

| Status | Code | Description |
|--------|------|-------------|
| 400 | - | Unknown metric type, empty dimension, or `startTime` after `endTime` |
| 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/...` |
