# Documents & News API

> Search and retrieve sentiment metrics for news articles, social media posts, and AI-curated news stories across multiple sources.

Base URL: `https://app.sentisense.ai/api/v1/documents`
Tier: Free

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#news--documents) · [Node](https://github.com/SentiSenseApp/sentisense-node#documents--news) · example: [news-search.ts](https://github.com/SentiSenseApp/sentisense-node/blob/main/examples/news-search.ts)

## Overview

The Documents & News API provides access to SentiSense's real-time sentiment metrics pipeline. Every document is enriched with AI-powered sentiment analysis, entity extraction, and reliability scoring.

**Data sources:** News (600+ publishers), Reddit, X (Twitter), Substack, YouTube

**Note on coverage:** aggregate metrics such as sentiment and mention counts incorporate signals from sources that are not individually retrievable as documents. Document counts returned by this API are therefore not a complete audit trail of a score.

**Key features:**
- Per-entity sentiment classification (e.g., an article about Apple and Google may be POSITIVE for Apple but NEGATIVE for Google)
- Smart search with natural language query parsing
- News clustering into "stories" that group related articles together
- Publisher reliability scoring

> **Note:** Document responses include sentiment metrics, reliability scores, source URLs, and timestamps. Use the `url` field to link through to the original article.

> **Building an AI agent?** See [How to feed real-time market news to your AI agent](/blog/news-feed-for-ai-agents/) for the subscribe-don't-scrape pattern, and how to use the `url` field to fetch full articles on your side.

---

## GET /ticker/{ticker}

Returns the latest news articles and social media posts mentioning a specific stock ticker.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock ticker symbol (e.g., `AAPL`) |
| `source` | string | No | all | Filter by source: `NEWS`, `REDDIT`, `X`, `SUBSTACK`, `YOUTUBE` |
| `days` | integer | No | 7 | Lookback period in days (1-365) |
| `hours` | integer | No | - | Lookback in hours, 1-8760 (overrides `days` if set). Out-of-range values return `400` |
| `limit` | integer | No | 200 | Max results (capped at 200) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/ticker/AAPL?source=NEWS&days=3&limit=10"
```

```python
from sentisense import SentiSenseClient
client = SentiSenseClient()
docs = client.get_documents_by_ticker("AAPL", source="NEWS", days=3, limit=10)
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `documents` | array | List of document objects (see Document schema below) |
| `totalCount` | int | Total matching documents |
| `searchTicker` | string | Ticker used for search |
| `source` | string | Source filter applied |
| `startDate` | string | Query start date (ISO) |
| `endDate` | string | Query end date (ISO) |

**Document object:**

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique document ID |
| `url` | string | Direct link to source article |
| `source` | string | `NEWS`, `REDDIT`, `X`, `SUBSTACK`, `YOUTUBE` |
| `sourceName` | string \| null | Publisher / source name (e.g. `"Reuters"`, `"r/wallstreetbets"`). Identifies the source; no headline or article text is included. |
| `published` | long | Epoch timestamp (seconds) |
| `averageSentiment` | double | Overall sentiment score (-1.0 to 1.0) |
| `reliability` | double | Publisher reliability score |
| `sentiment` | array | Per-entity sentiment entries (see Sentiment Entry below) |

**Sentiment Entry object:**

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string\|null | Stock ticker symbol (null for non-company entities) |
| `name` | string\|null | Human-readable entity name (e.g., "Apple Inc.", "Elon Musk") |
| `entityId` | string | Internal KB entity ID (e.g., `kb/company/1`). Not an address: `GET /entity/{entityId}` takes the entity `urlSlug`, which you get from `GET /api/v1/kb/entities/search?q=` by `name` |
| `entityType` | string | Entity type: `COMPANY`, `PERSON`, etc. |
| `sentiment` | string | `POSITIVE`, `NEGATIVE`, or `NEUTRAL` |

---

## GET /ticker/{ticker}/range

Returns documents for a stock ticker within a specific date range. Useful for historical research.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock ticker symbol |
| `startDate` | date (ISO) | Yes | - | Start date (e.g., `2025-01-01`) |
| `endDate` | date (ISO) | Yes | - | End date (e.g., `2025-01-31`) |
| `source` | string | No | all | Filter by source |
| `limit` | integer | No | 100 | Max results (capped at 200) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/ticker/TSLA/range?startDate=2025-01-01&endDate=2025-01-31&limit=50"
```

---

## GET /entity/{entityId}

Returns documents mentioning a SentiSense ontology entity (person, product, organization). Address the entity by its `urlSlug`, the same handle the [Metrics API](/docs/api/metrics/) takes; find one with `GET /api/v1/kb/entities/search?q=`.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityId` | path | Yes | - | Entity `urlSlug` (e.g. `Tim-Cook`) or stock ticker. Internal KB ids are not accepted |
| `source` | string | No | all | Filter by source |
| `days` | integer | No | 7 | Lookback in days (1-365) |
| `hours` | integer | No | - | Lookback in hours, 1-8760 (overrides days). Out-of-range values return `400` |
| `limit` | integer | No | 50 | Max results (capped at 200) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/entity/Tim-Cook?days=14"
```

---

## GET /search

Smart search with natural language query parsing. The query is analyzed to extract stock tickers, entity names, and free-text keywords, then used to find matching documents.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `query` | string | Yes | - | Natural language query (e.g., `"AAPL TSLA earnings"`) |
| `source` | string | No | all | Filter by source |
| `days` | integer | No | 7 | Lookback in days, 1 to 14. Larger values are capped at 14. |
| `limit` | integer | No | 200 | Max results (capped at 500) |

**Query examples:**
- `"AAPL"`: Documents about Apple
- `"AAPL, NVDA"`: Documents about Apple or NVIDIA
- `"Elon Musk, TSLA"`: Documents about Elon Musk or Tesla
- `"earnings report"`: Documents containing "earnings report"
- `"AAPL earnings beat"`: Apple + text search for "earnings beat"

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/search?query=NVDA+earnings&days=3&limit=20"
```

---

## GET /source/{source}

Returns the latest documents from a specific source type, across all stocks.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `source` | path | Yes | - | Source type: `NEWS`, `REDDIT`, `X`, `SUBSTACK`, `YOUTUBE` |
| `days` | integer | No | 7 | Lookback in days (1-365) |
| `hours` | integer | No | - | Lookback in hours, 1-8760 (overrides days). Out-of-range values return `400` |
| `limit` | integer | No | 100 | Max results (capped at 500) |
| `sort` | string | No | `latest` | Result ordering. `latest`: newest first. `top`: reliability-first, grouping recent documents into short freshness buckets and ranking by publisher reliability within each bucket, so recent content from high-authority publishers surfaces above low-authority floods. An unrecognized value returns `400`. |

Returns up to `limit` documents in the order `sort` asks for, counting only documents that carry an article body. Near-duplicate headlines are collapsed after that, and on X and YouTube some low-authority posts are filtered out, so a response can hold fewer than `limit` documents, at times well under. `totalCount` is the number returned, and there is no `offset`.

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/source/REDDIT?days=1&limit=50"
```

**Example Request (reliability-first ordering):**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/source/NEWS?sort=top&hours=6&limit=50"
```

---

## GET /stories

Returns the latest AI-curated news stories. Stories are clusters of related articles grouped by topic, with AI-generated titles and sentiment analysis.

**Access:** Public (list returns slim data: title, sentiment, impact, tickers)

> **Story detail** is available at [GET /stories/{clusterId}](#get-story-detail) and is **Quota-gated** (Free: 20 story detail views/mo, PRO: unlimited). The detail response includes the full narrative, bull/bear perspectives, and citation links.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `limit` | integer | No | 20 | Max stories (capped at 50) |
| `filterHours` | integer | No | - | Lookback window in hours, e.g. `720` for 30 days. This is the actual lookback control for this endpoint. |
| `days` | integer | No | 7 | Accepted but has no effect on the response; ignored server-side. Use `filterHours` instead. |
| `offset` | integer | No | 0 | Pagination offset |
| `ticker` | string | No | - | Filters the feed to story clusters tagged with that stock, e.g. `NVDA`. With a ticker set, `limit` caps at 20 instead of 50, while `filterHours` and `offset` still apply, and a ticker we do not track returns an empty list. The dedicated [GET /stories/ticker/{ticker}](#get-stories-by-ticker) path is still available. |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/stories?limit=10"
```

**Response Schema (Story object):**

| Field | Type | Description |
|-------|------|-------------|
| `cluster.id` | string | Story ID |
| `cluster.title` | string | AI-generated story title |
| `cluster.clusterSize` | int | Number of source articles |
| `cluster.averageSentiment` | double | Aggregate sentiment (-1.0 to 1.0) |
| `cluster.clusteredAt` | long | When the cluster was assembled by our pipeline (epoch seconds). |
| `cluster.createdAt` | long | **Deprecated 2026-05-16, removed on or after 2026-08-16.** Epoch millis. Use `cluster.clusteredAt`. |
| `cluster.storySource` | string | How the story was authored. `ORIGINAL` is an editorially authored SentiSense Original, `AI` is a pipeline-generated story. Always one of those two values. |
| `cluster.isLive` | boolean | True while the story is a developing one that we are actively updating. False otherwise. |
| `tickers` | array | Bare ticker symbols for programmatic use (e.g., `["AAPL"]`). Use these to filter or look up stocks. |
| `displayTickers` | array | Human-formatted labels for display only (e.g., `["Apple Inc (AAPL)"]`). Do not parse symbols out of these; use `tickers`. |
| `primaryEntityNames` | array | Primary entity names |
| `impactScore` | double | Impact score (0.0-10.0) |
| `brokeAt` | long \| null | When the story broke (epoch seconds). Null when no representative document timestamp is available. |

---

## GET /stories/search

Searches the AI-curated stories by a free-text query, the story-level counterpart of [GET /search](#get-search). The query is parsed the same way: explicit entity ids (`kb/company/1`), then entities recognised in the text (tickers, company names, people, organizations, products, topics), then the remaining words as keywords that must all appear in the story's own title or summary. Stories matching the recognised entities are searched first; if none match, the search retries on keywords alone. At most four keywords are used. A ticker is recognised in capitals (`MCD`) or as a cashtag (`$MCD`). Results are newest first.

**Access:** Public (same slim Story object as [GET /stories](#get-stories))

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `query` | string | Yes | - | Free text, e.g. `MCD traffic value menu`, `fed decision`, `kb/company/1 guidance`. A blank query is a 400. |
| `days` | integer | No | 7 | Lookback in days, 1 to 30. Stories older than 30 days are archived and not searchable. |
| `limit` | integer | No | 20 | Max stories (capped at 50) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/stories/search?query=MCD+traffic+value+menu&days=30"
```

**Response:** an array of Story objects, identical in shape to [GET /stories](#get-stories). Pass a story's `id` to [GET /stories/{clusterId}](#get-story-detail) for the full narrative.

---

## GET /stories/ticker/{ticker}

Returns news stories (clusters) for a specific stock ticker.

**Access:** Public

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock ticker symbol |
| `limit` | integer | No | 5 | Max stories (capped at 20) |

> **No lookback window on this endpoint.** It always returns the most recent stories for the ticker, so `days`, `hours`, and `filterHours` are ignored if you pass them. Use [GET /stories](#get-stories) with `filterHours` when you need a freshness window, or the `/documents/ticker/{ticker}` endpoints with `days` / `hours` for article-level lookback.

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/stories/ticker/AAPL?limit=10"
```

---

## GET /stories/{clusterId}

Returns the full detail for a single AI-curated story: the AI-written narrative, bull/bear perspectives, computed metrics, and citation links back to the original articles. Get the `clusterId` from the `id` field of the `/stories` or `/stories/ticker/{ticker}` list responses.

**Access:** Quota-gated. Free tier: 20 story detail views per month (each full-detail response consumes one view). PRO: unlimited. When the monthly allowance is exhausted, the endpoint stops returning full detail until the next monthly cycle.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `clusterId` | path | Yes | - | Story cluster ID (from the `/stories` list endpoints) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/documents/stories/CLUSTER_ID"
```

**Response Schema (flat story object, no `{isPreview, data}` wrapper):**

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Story cluster ID |
| `createdAt` | long | When the cluster was created |
| `lastUpdatedAt` | long \| null | Last content update |
| `title` | string | AI-generated story title |
| `summarizedContent` | string | AI-written summary (markdown; may contain ticker links, see below) |
| `narrativeBody` | string | Full AI-written narrative (markdown with `[docN]` citation references) |
| `bullishView` | string | AI-written bull perspective (markdown); `""` when the sources hold no bullish case, never `null` |
| `bearishView` | string | AI-written bear perspective (markdown); `""` when the sources hold no bearish case, never `null` |
| `aspectPerspectives` | array | Per-aspect entries `{aspectName, aspectType, bullishView, bearishView}`, where each view is an object `{hook, risksOrCatalysts, conclusion, confidence}`; empty array when there are none |
| `citationLinks` | object | Map of `docN` references in `narrativeBody` to public article URLs |
| `averageSentiment` | double | Aggregate sentiment (-1.0 to 1.0) |
| `momentumScore` | double \| null | Story momentum score |
| `aiConfidence` | double \| null | Model confidence in the story content |
| `clusterSize` | int | Number of source articles |
| `sourcesList` | array | Source types contributing to the story |
| `primaryCategory` | string | Primary story category |
| `dominantEventType` | string | Dominant event type |
| `publishersList` / `primaryPublisher` / `topPublishers` | array / string / array | Publisher name metadata (names only, no content) |
| `tickers` | array | Bare ticker symbols for programmatic use |
| `displayTickers` | array | Human-formatted labels for display only |
| `primaryEntityNames` | array | Primary entity names |
| `relatedEntities` | array | Related entities: `{entityId, name, ticker, type, isPrimary, frequency}` |
| `archived` | boolean | Whether the story is archived |
| `totalDocuments` | int | Total documents in the cluster |
| `storySource` | string | How the story was authored. `ORIGINAL` is an editorially authored SentiSense Original, `AI` is a pipeline-generated story. Always one of those two values. |
| `isLive` | boolean | True while the story is a developing one that we are actively updating. False otherwise. |
| `timeline` | array | Dated updates on the story, newest first. Each entry is `{publishedAt, updateType, content}`: `publishedAt` is epoch millis, `updateType` is `INITIAL`, `UPDATE` or `CORRECTION`, and `content` is SentiSense-written markdown whose `[docN]` references resolve through `citationLinks`. Empty array when a story has no updates. |

An empty side means SentiSense found no honest case on that side in the sources; it is never `null`. At the aspect level an empty view is `{"hook": "", "risksOrCatalysts": [], "conclusion": "", "confidence": 0.0}`, so check the text fields rather than the confidence, and show the side as having no case rather than writing one.

> **No headlines policy:** consistent with the rest of the Documents API, publisher headlines, article text, and images are never included. The `title`, `narrativeBody`, and perspectives are AI-generated by SentiSense, and `citationLinks` point to the original sources.
>
> **Ticker links:** every markdown field above (`summarizedContent`, `narrativeBody`, the perspectives and timeline `content`) may link tickers to their SentiSense stock page as an absolute URL, e.g. `[KMX](https://app.sentisense.ai/stocks/KMX)`. To show the text without markdown, keep the link text and drop the URL; the story's tickers are also listed in `tickers`.
>
> **Inline images (additive, Originals):** `narrativeBody` and timeline `content` may contain standard markdown image links (`![caption](url)`) pointing at SentiSense-authored charts on `sentisense.ai` hosts. The field stays a markdown string, so nothing changes for clients that already render or ignore markdown; publisher images are still never included.
