# Earnings Analysis API

> The earnings lifecycle as one API family: who reports (calendar), what management changed in its SEC filings (risk-factor diffs), the per-quarter analysis of what was actually reported, and the AI takeaway (insights).

Base URL: `https://app.sentisense.ai/api/v1/`
Tier: PRO (free preview available)

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#earnings) · [Node](https://github.com/SentiSenseApp/sentisense-node#earnings)

## Overview

An earnings event is not a single moment, it is a short lifecycle, and this API family follows it end to end. First you know who reports and when: the **Earnings Calendar**. Then you read what management changed in its own words, through deterministic diffs of the risk-factor sections of consecutive SEC filings: **What Changed**. Then, once the company reports, you read the quarter itself: the **Earnings Analysis Report**, plus the reported numbers (**fundamentals and company KPIs**) and the takeaway as AI-generated signals (**Insights**).

Three endpoints are documented in full below. The Calendar, Fundamentals and KPIs, and Insights members of the family already have their own reference pages and are summarized here with links, not restated.

**In this family:**
- **What Changed** (this page): risk-factor (Item 1A) diffs of consecutive 10-K and 10-Q filings, with added, removed, and modified passages and a materiality score.
- **Earnings Analysis Report** (this page): the per-quarter readout, one object per fiscal quarter, carrying the headline, KPI highlights with year-over-year deltas, guidance language, and a summary of the earnings call.
- **Recently reported** (this page): the cross-ticker view of which covered companies reported in a recent window.
- **Earnings Calendar**: forward-looking report dates with session timing, fiscal quarter, confirmation status, and consensus EPS.
- **Reported Financials and KPIs**: standardized financial-statement data plus curated company KPIs.
- **Earnings Insights**: AI-generated signals, including earnings-driven signal types.

**Where the quarter is the unit.** The analysis is organized by fiscal quarter, and everything else attaches to one: a filing diff belongs to the quarter it covers, and consensus EPS from the Calendar is the anchor a headline beats or misses. Build a quarter view by pairing `GET /api/v1/stocks/{ticker}/earnings-summaries` with the filings that fall near its `reportDate`, rather than treating filings and results as two unrelated lists.

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

---

## What Changed: SEC risk-factor diffs

Risk-factor language is written by lawyers and changes reluctantly. When a company rewrites large parts of that section, it is often the earliest public signal of a newly disclosed risk. What Changed compares the Item 1A Risk Factors section of each new 10-K or 10-Q against the prior filing of the same form (10-K vs prior 10-K, 10-Q vs prior 10-Q) and returns excerpts of the passages that were added, removed, or modified, along with the new key terms.

**Use cases:**
- Detect newly disclosed risk factors shortly after a 10-K or 10-Q is filed.
- Rank a watchlist by how heavily each company revised its risk language this quarter.
- Pull the before and after excerpts and the new terms for research or summarization.
- Cross-reference filing changes with sentiment, insider trading, and analyst revisions.

**Coverage:** roughly 500 large-cap US companies, including 99% of the S&P 500 plus a set of widely followed software and semiconductor names outside the index. It tracks the Item 1A Risk Factors section of **10-K** (annual) and **10-Q** (quarterly) filings and returns up to about **three sequential filing-to-filing comparisons per form**: the annual filings back to 2023 and roughly the last year of quarterly filings. Nearly every comparison returns full `diff` detail. The one structural exception is the earliest filing held for a given form: it has no prior filing to compare against, so it returns the per-filing summary fields (materiality score, the no-material-changes flag, and the EDGAR link) without the `diff` object. Treat `diff` as optional on every entry rather than assuming it is present. New filings are typically reflected within 48 hours of appearing on the SEC public filing system. Coverage is expanding over time.

Tickers outside the covered set return a **successful, empty response** (`200` with an empty `data` array), not an error. Use canonical ticker symbols: `GOOGL` (not `GOOG`) and `BRK.B` (not `BRK-B`).

**Free preview:** the per-filing summary (form type, filing and report dates, section, materiality score, no-material-changes flag, EDGAR link) with `totalCount`. The full diff detail (changed passages, ratios, and new terms) is PRO-only.

## GET /api/v1/stocks/{ticker}/what-changed

Returns the ticker's diffed filings, newest first. Each entry compares one filing's Risk Factors section against the prior filing of the same form.

**Authentication:** PRO required for diff detail. Free users receive the summary fields and `totalCount`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock ticker symbol, canonical form (e.g. `AAPL`) |
| `form` | query | No | both | Filter to `10-K` or `10-Q` |
| `limit` | query | No | 4 | Max entries returned (1 to 12). Values above 12 are capped at 12. Values below 1 are rejected with `400 invalid_limit`. |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/AAPL/what-changed?limit=1"
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `isPreview` | boolean | `true` when the caller is on the FREE tier |
| `previewReason` | string | `"PRO_REQUIRED"` or `null` |
| `totalCount` | integer | Preview responses only: total diffed filings available |
| `upgrade` | object | Present only when `isPreview` is `true`. Carries `plan`, `message`, `price`, `url` and `relay`: surface `message` and `url` to your user in one line, then continue with the preview `data` |
| `data` | array | Filing comparisons, newest first (see below) |

**Filing entry:**

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string | Canonical ticker |
| `formType` | string | `10-K` or `10-Q` |
| `accessionNo` | string | Accession number of the newer filing |
| `filedAt` | string | Filing date, `YYYY-MM-DD` |
| `reportDate` | string | Fiscal period the filing covers, `YYYY-MM-DD` |
| `section` | string | Compared section, `Risk Factors` (Item 1A) today |
| `materialityScore` | number | A 0 to 1 measure of how much the section changed versus the prior filing of the same form |
| `noMaterialChanges` | boolean | `true` when the filer states the section has no material changes (common in 10-Qs) |
| `edgarUrl` | string | Link to the filing on the SEC public filing system |
| `diff` | object | PRO only: full diff detail (see below) |

**Diff detail (PRO):**

| Field | Type | Description |
|-------|------|-------------|
| `blocks` | array | Changed regions: `{op, similarity, oldExcerpt, newExcerpt, oldParagraphs, newParagraphs}` where `op` is `added`, `removed`, or `modified`. `oldExcerpt` and `newExcerpt` are capped at 400 characters and end with `...` when truncated; a block spanning many paragraphs still returns one bounded excerpt |
| `paragraphsAdded` | integer | Paragraphs with no counterpart in the prior filing |
| `paragraphsRemoved` | integer | Prior-filing paragraphs that were dropped |
| `paragraphsModified` | integer | Paragraphs rewritten in place |
| `charsAdded` / `charsRemoved` | integer | Character-level change volume |
| `changedRatio` | number | Share of the section affected by changes, 0 to 1 |
| `noveltyRatio` | number | Share of the new section that is genuinely new language, 0 to 1 |
| `materialityScore` | number | Composite of the changed and novelty ratios, 0 to 1 |
| `topNewTerms` | array | Most distinctive terms appearing in the new filing but not the prior one |
| `identical` | boolean | `true` when the sections match after normalization |
| `noMaterialChanges` | boolean | Mirrors the entry-level flag |

**Example Response (PRO, truncated):**

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": [
    {
      "ticker": "AAPL",
      "formType": "10-Q",
      "accessionNo": "0000320193-26-000020",
      "filedAt": "2026-07-31",
      "reportDate": "2026-06-27",
      "section": "Risk Factors",
      "materialityScore": 0.769,
      "noMaterialChanges": false,
      "edgarUrl": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000020/0000320193-26-000020-index.htm",
      "diff": {
        "blocks": [
          {
            "op": "modified",
            "similarity": 0.722,
            "oldExcerpt": "The Company's business, reputation, results of operations, financial condition and stock price can be materially and adversely affected by a number of factors, whether currently known or unknown, including those described in Part I, Item 1A of the 2025 Form 10-K under the heading \"Risk Factors.\" Except as set forth below, there have been no material changes to the Company's risk factors since the ...",
            "newExcerpt": "The Company's business, reputation, results of operations, financial condition and stock price can be materially and adversely affected by a number of factors, whether currently known or unknown, including those described in Part I, Item 1A of the 2025 Form 10-K and Part II, Item 1A of the Form 10-Q for the quarter ended March 28, 2026 (the \"second quarter 2026 Form 10-Q\"), in each case under the ...",
            "oldParagraphs": 1,
            "newParagraphs": 1
          }
        ],
        "paragraphsAdded": 0,
        "paragraphsRemoved": 0,
        "paragraphsModified": 13,
        "charsAdded": 7214,
        "charsRemoved": 10213,
        "changedRatio": 0.912,
        "noveltyRatio": 0.2299,
        "materialityScore": 0.769,
        "topNewTerms": ["obtain", "depend", "quantities", "commercially", "computing"],
        "identical": false,
        "noMaterialChanges": false
      }
    }
  ]
}
```

FREE tier: same shape with `isPreview: true`, `previewReason: "PRO_REQUIRED"`, a `totalCount` of the diffed filings available, and each entry carrying the summary fields without the `diff` object.

**Notes:**
- Trivial rewording (singular/plural, punctuation) is ignored; changed numbers are never ignored.
- An empty `data` array means the ticker has no diffed filings in the covered set yet, not an error.
- This endpoint reports factual differences between public SEC filings for research and educational purposes. It is not investment advice.

---

## Earnings analysis reports: what a company actually reported

A quarter's results arrive as a press release, a filing, and a call, none of which is a data structure. The earnings analysis report is the assembled version: one object per fiscal quarter carrying the editorial headline, the KPI highlights that matter for that company with year-over-year deltas, the guidance language as management phrased it, and a summary of the earnings call. It is the readout the SentiSense app itself renders, available as a single call rather than something you reassemble from four endpoints.

**Use cases:**
- Give a research agent a company's reported quarters in one call, already organized by fiscal period. History began with the mid-2026 reporting season and grows each season, so fewer quarters than `limit` can come back.
- Compare guidance language across quarters to see when the tone changed.
- Read the call summary alongside the reported KPIs instead of parsing a transcript.
- Anchor a beat-or-miss judgement by pairing the headline with consensus EPS from the Calendar.

**Freshness:** a quarter typically appears within 48 hours of the company reporting, and the call summary can arrive after the press-release content for the same quarter. Read `generatedAt` (and `transcriptGeneratedAt`) rather than assuming a fixed lag, and expect a quarter to gain the call summary on a later read.

**Coverage:** the actively curated US equity universe, expanding each earnings season. A ticker with no stored quarter returns a **successful, empty response** (`200` with an empty `data` array), not an error. Use canonical ticker symbols: `GOOGL` (not `GOOG`) and `BRK.B` (not `BRK-B`).

**Free preview:** the latest quarter only, shaped rather than truncated. FREE keys receive the headline and report date in full, up to two marquee KPI cards at their latest value, topic labels for the summary and the call in place of their bodies, and whether the quarter carries guidance (with its source and a **direction**) in place of the guidance language. PRO keys receive every hydrated quarter in full.

## GET /api/v1/stocks/{ticker}/earnings-summaries

Returns the ticker's stored quarters, newest first.

**Authentication:** PRO required for the full quarter and for history. Free keys receive the shaped latest quarter and `totalCount`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock ticker symbol, canonical form (e.g. `AAPL`) |
| `limit` | query | No | 12 | Max quarters returned (1 to 40). Values above 40 are capped at 40. Values below 1 are rejected with `400 invalid_limit`. FREE keys receive one quarter regardless of this value. |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/AAPL/earnings-summaries?limit=4"
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `isPreview` | boolean | `true` when the caller is on the FREE tier |
| `previewReason` | string | `"PRO_REQUIRED"` or `null` |
| `totalCount` | integer | Number of indexed quarters, present on both PRO and FREE responses |
| `upgrade` | object | Present only when `isPreview` is `true`. Carries `plan`, `message`, `price`, `url` and `relay`: surface `message` and `url` to your user in one line, then continue with the preview `data` |
| `data` | array | Hydrated quarters returned, newest first; its length can be shorter than `totalCount` when a body is unavailable or the response is limited |

**Quarter entry (PRO):**

| Field | Type | Description |
|-------|------|-------------|
| `fiscalPeriod` | string | Display fiscal period, e.g. `"Q2 FY2026"` |
| `reportDate` | string | Date the results were reported, `YYYY-MM-DD` |
| `headline` | string | One-line editorial summary of the quarter |
| `summaryMd` | string | Markdown body summarizing the reported results |
| `kpiHighlights` | array | `{label, value, yoy}`; `value` and `yoy` are display strings, `yoy` may be absent |
| `guidance` | string | Forward-guidance language as reported; absent when the quarter carries none |
| `hasTranscript` | boolean | `true` when a summary of the earnings call exists for this quarter |
| `transcriptSummaryMd` | string | Markdown body summarizing the call; absent when `hasTranscript` is `false` |
| `transcriptHighlights` | array | Call-specific `{label, value, yoy}` entries; absent when there is no call summary |
| `transcriptGeneratedAt` | integer | Epoch seconds the call summary was generated; absent when there is no call summary |
| `sources` | array | `{title, url}` citations backing the quarter |
| `generatedAt` | integer | Epoch seconds the quarter summary was generated |
| `source` | string | Provenance of the quarter summary: `"press_release"` or `"transcript"` |

**Quarter entry (FREE preview):** `fiscalPeriod`, `reportDate` and `headline` in full, plus `kpiHighlights` as up to two `{label, value}` cards, `kpiHighlightCount` (how many cards the full quarter carries), `summaryTopics` and `transcriptTopics` (topic labels only, never body text or figures: the markdown headings and bold labels of each body when it has any, otherwise the labels of its highlight cards, the KPI cards for the summary and the call highlights for the transcript; a label carrying a figure is dropped, so a list can be empty), `hasTranscript`, `hasGuidance` (`true` when the press release's guidance line carries guidance or, when it does not, when the call summary carries guidance or outlook language; `false` when neither does, or management said it gives none), `guidanceSource` (`"press_release"` or `"transcript"`, the call summary; absent when `hasGuidance` is `false`), `guidanceDirection` (`"RAISED"`, `"CUT"`, `"HELD"`, `"MIXED"`, or absent: a keyword classification of the guidance language from that same source, never management's own label), `generatedAt` and `source`. The preview never carries a body, a KPI history, or a guidance figure.

**Example Response (PRO, truncated):**

```json
{
  "isPreview": false,
  "previewReason": null,
  "totalCount": 1,
  "data": [
    {
      "fiscalPeriod": "Q2 FY2026",
      "reportDate": "2026-05-01",
      "headline": "Services revenue set a record and margins expanded",
      "summaryMd": "## Segment performance\n\n- **Services revenue**: $00.0B, up 00%\n- **Product revenue**: $00.0B, flat",
      "kpiHighlights": [
        {"label": "Revenue", "value": "$00.0B", "yoy": "+00% YoY"},
        {"label": "EPS", "value": "$0.00", "yoy": "+00% YoY"}
      ],
      "guidance": "Raised the full-year revenue outlook and reaffirmed the margin range",
      "hasTranscript": true,
      "transcriptSummaryMd": "## Demand\n\n- **Enterprise**: management described bookings as ahead of plan",
      "transcriptGeneratedAt": 1780000000,
      "sources": [
        {"title": "Q2 FY2026 results press release", "url": "https://www.example.com/investor/q2-fy2026"}
      ],
      "generatedAt": 1779990000,
      "source": "press_release"
    }
  ]
}
```

**Notes:**
- Figures in the example above are placeholders. Call the endpoint for real values.
- Absence is explicit: a quarter with no call summary sets `hasTranscript: false` rather than omitting the concept, so a client can say "no call summary yet" instead of rendering nothing.
- `guidance` is prose, not a number. Deriving a direction from it is the FREE preview's job (`guidanceDirection`); PRO callers get the language (the release `guidance`, and the call summary when the release carries none) and can classify it themselves.
- This endpoint summarizes public company disclosures for research and educational purposes. It is not investment advice.

---

## GET /api/v1/stocks/{ticker}/earnings/reactions

What the stock actually did on each of its last twelve earnings announcements: the signed close-to-close move of the session that traded on the news, newest first. Pair the series with `expectedMove1d` (or the one-sigma `expectedMove1s1d`) from `GET /api/v1/stocks/{ticker}/options/summary` to see whether the options market is pricing this event above or below what the last three years of prints delivered.

**Authentication:** API key required. There is no tier gate on this endpoint; every key receives the full series.

**Parameters:** none beyond the `ticker` path variable.

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/NVDA/earnings/reactions"
```

**Example Response:**

```json
{
  "ticker": "NVDA",
  "asOf": "2026-08-21",
  "reactions": [
    {
      "reportDate": "2026-05-20",
      "timing": "AMC",
      "priorClose": 223.47,
      "nextClose": 219.51,
      "movePct": -1.77
    }
  ]
}
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string | Canonical ticker the reactions belong to |
| `asOf` | string | ISO date `YYYY-MM-DD` the response was produced |
| `reactions` | array | Past announcements, newest first, at most 12 (see below) |

**Row:**

| Field | Type | Description |
|-------|------|-------------|
| `reportDate` | string | ISO date `YYYY-MM-DD` the results were announced |
| `timing` | string or null | `"AMC"`, `"BMO"`, or `null`. See "Which session a move belongs to" below |
| `priorClose` | number | Close of the session immediately before the reaction session |
| `nextClose` | number | Close of the reaction session |
| `movePct` | number | Signed percent change from `priorClose` to `nextClose`, two decimals |

**Which session a move belongs to.** A company that announces after the close moves the *next* trading session; one that announces before the open moves that same session. Getting this backwards does not produce a slightly wrong number, it produces an unrelated day, so the session is established from the acceptance timestamp of the company's Item 2.02 8-K, the earnings release itself, which the SEC publishes to the minute.

- `timing: "AMC"` means it was furnished at or after 16:00 ET, so `nextClose` is the next trading day and `priorClose` is the report date.
- `timing: "BMO"` means it was furnished before 09:30 ET, so `nextClose` is the report date and `priorClose` is the trading day before it.
- `timing: null` means we could not place the announcement outside trading hours, and the move was measured on the report date itself. **Treat null as "the session was inferred, not observed."** If your analysis needs certainty, filter these rows out. The field is always present; it is never omitted from the row.

**Notes:**
- Past announcements only. A company that reported after today's close has no completed reaction session yet and appears once that session closes.
- A quarter we cannot measure is absent rather than approximated: no row is ever computed across a gap in price history.
- An empty `reactions` array means we have no measured history for that ticker, not an error. Unknown tickers return the same shape.
- `movePct` is a realized price move, not a forecast and not investment advice.

---

## GET /api/v1/earnings/recent

The cross-ticker view: which covered companies reported on or after `today - days`, newest first. Use it to drive a post-earnings sweep ("who reported this week"), then follow up per ticker with the per-quarter analysis above.

**Authentication:** API key required. There is no tier gate on this endpoint; every key receives the full window it asks for.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `days` | query | No | 7 | Look-back window in days (1 to 31). Values above 31 are capped at 31. Values below 1 are rejected with `400 invalid_days`. |
| `limit` | query | No | 50 | Max rows returned (1 to 100). Values above 100 are capped at 100. Values below 1 are rejected with `400 invalid_limit`. |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/earnings/recent?days=7&limit=25"
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `isPreview` | boolean | Always `false` on this endpoint |
| `previewReason` | string | Always `null` on this endpoint |
| `data` | array | Reported quarters, newest first (see below) |

**Row:**

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string | Canonical ticker that reported |
| `fiscalPeriod` | string | Display fiscal period, e.g. `"Q2 FY2026"` |
| `reportDate` | string | Date the results were reported, `YYYY-MM-DD` |
| `headline` | string | One-line editorial summary of the quarter |
| `hasTranscriptSummary` | boolean | `true` when a summary of the earnings call exists for this quarter |
| `generatedAt` | integer | Epoch seconds of the latest content written for this quarter |

**Notes:**
- The window is bounded by `reportDate`, so a quarter reported inside the window appears even if its call summary lands later.
- An empty `data` array means nobody in the covered set reported in that window, not an error.
- This is the only backward-looking earnings feed. The Calendar API is forward-looking and covers scheduled dates, not results.

---

## GET /api/v1/earnings/statistics

The market-wide view: of the companies that reported in a given window, how many cleared the analyst estimate, and how the market actually traded them. Every other endpoint on this page answers a question about one company. This one answers a question about the market, and it is the only place the beat rate and the realized price reaction are joined.

The response is structured rather than written as a sentence. Each figure ships with its numerator, both of its denominators, the span it covers, the trailing baseline it is judged against, and the version of the rules that produced it, so a caller can re-derive every rate instead of trusting it.

**Authentication:** API key required. There is no tier gate on this endpoint; every key receives the full body.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `window` | query | No | `last_completed_week` | One of `last_completed_week`, `week_to_date`, `trailing_52w`, `all_time`. Any other value is rejected with `400 invalid_window`. |

Windows are ISO weeks (Monday through Sunday) evaluated in America/New_York. The default is the last finished week because a figure published mid-week gets revised underneath its readers; `week_to_date` returns the running figure and marks itself partial. `trailing_52w` and `all_time` are long spans, so they carry no baseline and no deviation.

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/earnings/statistics?window=last_completed_week"
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `isPreview` | boolean | Always `false` on this endpoint |
| `previewReason` | string | Always `null` on this endpoint |
| `data` | object | The statistics block (see below) |

**`data`:**

| Field | Type | Description |
|-------|------|-------------|
| `calculationVersion` | string | Revision of the rules. A change means figures for the same window may differ from a previous response, that is, a restatement rather than new data |
| `asOf` | integer | Epoch seconds when the figures were computed |
| `window` | object | `key`, `kind`, `startDate`, `endDate`. `key` is stable and unique per span, so it is safe to cache on |
| `eventsInWindow` | integer | Every reported quarter in the window |
| `classifiedEvents` | integer | Quarters carrying both an estimate and a reported EPS. The beat, miss and inline denominator |
| `unclassifiedEvents` | integer | Quarters missing an EPS figure, excluded from `classifiedEvents` |
| `distinctTickers` | integer | Distinct companies behind `eventsInWindow`. Statistics are event-weighted, not per-company |
| `completedReactions` | integer | Classified quarters whose trading session has been measured. The reaction denominator, and never equal to `classifiedEvents` |
| `pendingReactions` | integer | Classified quarters still waiting on their trading session to close |
| `coverageRatio` | number | `completedReactions` over `classifiedEvents`, as a fraction |
| `sufficientData` | boolean | `false` when the window misses a publication floor. Check this before quoting a rate |
| `insufficientDataReason` | string | `"SAMPLE_BELOW_FLOOR"` or `"COVERAGE_BELOW_FLOOR"`, absent when `sufficientData` is `true` |
| `beat`, `miss`, `inline` | object | One block per outcome, all three the same shape (see below) |
| `averageMovePct` | number | Mean signed session move across every measured quarter, as a percent |
| `baseline` | object | The trailing span this window is judged against. Absent on `trailing_52w` and `all_time` |
| `deviation` | object | Signed distance from the baseline, plus the materiality flags. Absent when there is no baseline |
| `thresholds` | object | The constants behind `sufficientData` and the materiality flags |

**Outcome block (`beat`, `miss`, `inline`):**

| Field | Type | Description |
|-------|------|-------------|
| `count` | integer | Quarters that landed this way. Numerator of `rate` |
| `rate` | number | `count` over `classifiedEvents`, as a fraction |
| `withReaction` | integer | Of `count`, how many have a measured trading session. Denominator of `fellRate` |
| `fell`, `rose`, `flat` | integer | Of `withReaction`, how the session closed |
| `fellRate` | number | `fell` over `withReaction`, as a fraction |
| `averageMovePct` | number | Mean signed session move of those quarters, as a percent |

**Example Response** (values are illustrative):

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "calculationVersion": "1.0.0",
    "asOf": 1234567890,
    "window": {
      "key": "2026-W01",
      "kind": "COMPLETED_WEEK",
      "startDate": "2025-12-29",
      "endDate": "2026-01-04"
    },
    "eventsInWindow": 120,
    "classifiedEvents": 118,
    "unclassifiedEvents": 2,
    "distinctTickers": 118,
    "completedReactions": 104,
    "pendingReactions": 14,
    "coverageRatio": 0.8814,
    "sufficientData": true,
    "beat": {
      "count": 80,
      "rate": 0.6780,
      "withReaction": 71,
      "fell": 30,
      "rose": 40,
      "flat": 1,
      "fellRate": 0.4225,
      "averageMovePct": 1.1
    },
    "miss": {
      "count": 34,
      "rate": 0.2881,
      "withReaction": 30,
      "fell": 19,
      "rose": 11,
      "flat": 0,
      "fellRate": 0.6333,
      "averageMovePct": -2.2
    },
    "inline": {
      "count": 4,
      "rate": 0.0339,
      "withReaction": 3,
      "fell": 2,
      "rose": 1,
      "flat": 0,
      "fellRate": 0.6667,
      "averageMovePct": -0.4
    },
    "averageMovePct": 0.1,
    "baseline": {
      "window": {
        "key": "2026-W01-trailing52w",
        "kind": "TRAILING_BASELINE",
        "startDate": "2024-12-30",
        "endDate": "2025-12-28"
      },
      "classifiedEvents": 2400,
      "completedReactions": 2150,
      "distinctTickers": 900,
      "beatRate": 0.7300,
      "beatsFellRate": 0.4400,
      "coverageRatio": 0.8958,
      "sufficientData": true
    },
    "deviation": {
      "beatRate": -0.0520,
      "beatsFellRate": -0.0175,
      "beatRateIsMaterial": false,
      "beatsFellRateIsMaterial": false
    },
    "thresholds": {
      "minClassifiedEvents": 30,
      "minCoverageRatio": 0.80,
      "baselineWeeks": 52,
      "beatRateDeviation": 0.10,
      "reactionDivergence": 0.07
    }
  }
}
```

**Notes:**
- **Read the units from the field names.** Anything ending in `Rate` or `Ratio` is a fraction in `[0,1]`: `0.7300` means 73.00%. Anything ending in `Pct` is a signed percent: `-2.2` means a 2.2% fall.
- **A rate whose denominator is zero is `null`, never `0`.** There is a real difference between no beats falling and no beat having been measured yet.
- **The two denominators are different on purpose.** `classifiedEvents` counts quarters with both EPS figures; `completedReactions` counts the subset whose trading session has closed. They are never equal, because a company that reports after the close has a known surprise hours before the session that trades it, and because our reaction coverage spans slightly fewer companies than our estimate coverage. Dividing a reaction statistic by the wrong one understates it.
- **Check `sufficientData` before quoting anything.** A thin or still-settling window still returns real counts and rates, because that is more useful than an error, but the flag says the figure is not fit to publish. `thresholds` explains why, so you can apply your own bar instead of ours.
- **The baseline excludes the window it judges.** It is the 52 ISO weeks ending the day before the window starts. A baseline containing the observation would move toward it and shrink the very deviation the comparison exists to detect.
- Beat, miss and inline are classified from reported EPS against the estimate directly, never from a rounded surprise percentage.
- These are realized historical statistics, not forecasts and not investment advice.

---

## GET /api/v1/earnings/ranked

The ranked cross-ticker view puts the recent reports that matter most beside the upcoming reports most worth watching. Use it when you need a prioritized earnings sweep with EPS surprise, price reaction, market cap and SentiSense Score context. Use `/earnings/recent` for a simple newest-first list of reported quarters, and `/calendar/earnings` for the broader schedule without importance ranking.

**Authentication:** API key required. PRO keys receive every row allowed by the requested limits. FREE keys receive the first 3 rows in each section, while each section's `totalInWindow` still reports the complete window count. `previewReason` is `"PRO_REQUIRED"` for FREE keys and `null` for PRO keys.

**Parameters:**

| Name | Type | Default | Bounds | Note |
|------|------|---------|--------|------|
| `reportedDays` | integer | 14 | 1 to 31 | Look-back window in days. Values above 31 are capped; values below 1 return `400 invalid_reportedDays`. |
| `reportedLimit` | integer | 12 | 1 to 50 | Maximum reported rows before tier preview. Values above 50 are capped; values below 1 return `400 invalid_reportedLimit`. |
| `upcomingDays` | integer | 7 | 1 to 31 | Forward window in days. Values above 31 are capped; values below 1 return `400 invalid_upcomingDays`. |
| `upcomingLimit` | integer | 12 | 1 to 50 | Maximum upcoming rows before tier preview. Values above 50 are capped; values below 1 return `400 invalid_upcomingLimit`. |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/earnings/ranked?reportedDays=14&reportedLimit=5&upcomingDays=7&upcomingLimit=5"
```

**Example Response** (values are illustrative):

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "asOf": 1788987600,
    "rankingVersion": "1.5.0",
    "reported": {
      "windowStart": "2026-08-26",
      "windowEnd": "2026-09-09",
      "totalInWindow": 42,
      "rows": [
        {
          "ticker": "NVDA",
          "reportDate": "2026-08-27",
          "fiscalPeriod": "Q2 FY2027",
          "headline": "Data center growth offsets softer gaming demand",
          "hasTranscriptSummary": true,
          "estimateEps": 1.01,
          "actualEps": 1.08,
          "surprisePct": 6.93,
          "outcome": "BEAT",
          "movePct": -2.14,
          "reactionPending": false,
          "awaitingConsensus": false,
          "marketCap": 4210000000000,
          "sentisenseScore7d": 18.4,
          "scoreChange7d": 3.2,
          "importance": 0.96
        },
        {
          "ticker": "ORCL",
          "reportDate": "2026-09-08",
          "fiscalPeriod": "Q1 FY2027",
          "headline": "Cloud infrastructure growth accelerates",
          "hasTranscriptSummary": false,
          "estimateEps": 1.48,
          "actualEps": 1.52,
          "surprisePct": 2.70,
          "outcome": "BEAT",
          "reactionPending": true,
          "liveReactionPct": 1.35,
          "awaitingConsensus": false,
          "marketCap": 520000000000,
          "sentisenseScore7d": 9.7,
          "scoreChange7d": 1.1,
          "importance": 0.88
        },
        {
          "ticker": "RH",
          "reportDate": "2026-09-09",
          "fiscalPeriod": "Q2 FY2027",
          "hasTranscriptSummary": false,
          "estimateEps": 0.38,
          "actualEps": 0.51,
          "surprisePct": 34.21,
          "outcome": "BEAT",
          "reactionPending": true,
          "afterHoursReactionPct": 4.13,
          "awaitingConsensus": false,
          "marketCap": 4100000000,
          "sentisenseScore7d": 6.2,
          "scoreChange7d": 0.8,
          "importance": 0.71
        }
      ]
    },
    "upcoming": {
      "windowStart": "2026-09-09",
      "windowEnd": "2026-09-16",
      "totalInWindow": 31,
      "rows": [
        {
          "ticker": "ADBE",
          "companyName": "Adobe Inc.",
          "earningsDate": "2026-09-10",
          "earningsTime": "after_close",
          "confirmed": true,
          "estimatedEps": 5.18,
          "marketCap": 142000000000,
          "sentisenseScore7d": -4.6,
          "scoreChange7d": -2.3,
          "importance": 0.84
        }
      ]
    }
  }
}
```

**Fields:**

- `isPreview` is false for PRO keys and true for FREE keys. `previewReason` is `"PRO_REQUIRED"` or `null` only.
- `upgrade` is present only on a FREE preview and carries the upgrade hint. `data` contains the ranked response described below.
- `asOf` is epoch seconds when the ranking was computed. `rankingVersion` identifies the ranking-rule revision.
- `reported` and `upcoming` are anchored to today's date in `America/New_York`, with both bounds inclusive: `today - reportedDays` through today, and today through `today + upcomingDays`. Each section carries ISO date bounds, `totalInWindow` before row limits or tier preview, and `rows` ordered from highest to lowest `importance`.
- Optional row fields are omitted when unavailable: `hasTranscriptSummary`, `fiscalPeriod`, `headline`, `estimateEps`, `actualEps`, `surprisePct`, `movePct`, `reactionPending`, `liveReactionPct`, `afterHoursReactionPct`, `awaitingConsensus`, `marketCap`, `sentisenseScore7d`, `scoreChange7d`, `companyName`, `earningsTime`, and `estimatedEps`. An omitted boolean does not mean false.
- `ticker` is the canonical symbol. Upcoming rows carry `earningsDate` and `confirmed`, which says whether the company confirmed that date; `companyName` is optional.
- Reported rows carry `reportDate`; the curated `fiscalPeriod`, `headline`, and `hasTranscriptSummary` are optional. When present, `hasTranscriptSummary` says whether a call summary is available.
- `estimateEps` and `actualEps` are the consensus and reported EPS values for a reported quarter. `estimatedEps` is the consensus EPS value for an upcoming report; any of these can be absent when the source has not supplied it.
- `surprisePct` is the signed EPS surprise as a percent of the absolute estimate, omitted when either EPS figure is unavailable or the estimate is zero. `movePct` is the signed close-to-close percent of the session that traded on the news: the next session for an after-close report, or the same session for a before-open report; `-2.14` means a 2.14% decline.
- `importance` is a fraction in `[0, 1]`. `sentisenseScore7d` is the signed, unbounded seven-day average SentiSense Score; `scoreChange7d` is that average minus its 30-day average, in signed score units.
- `marketCap` is the issuer's current market capitalization in US dollars.
- `reactionPending` is true only when the final reaction is missing and the reacting session may not have closed. `liveReactionPct` is a provisional in-session reading in signed percent from a delayed snapshot while the reaction is not final; its presence does not imply the market is open, and it is omitted once `movePct` is final. `afterHoursReactionPct` is the extended-hours move, in signed percent against the report day's regular close. It appears from 16:00 ET on the report date until the reacting session opens at 09:30 ET (across the weekend for a Friday report), for a company whose calendar entry says it reports after the close, and only while `movePct` and `liveReactionPct` are both absent; `reactionPending` stays true alongside it. On the reacting morning, once pre-market trading has begun, it can carry the pre-market move instead, against the same close. The three reaction readings are mutually exclusive, so a row carries at most one. Read it as the extended-hours move, not as the reaction: the close-to-close measurement that replaces it covers a different interval.
- `awaitingConsensus` is true when a reported quarter exists but its estimate and actual EPS consensus row has not arrived yet.
- `outcome` is `BEAT`, `MISS`, `INLINE`, or `UNCLASSIFIED`.
- When present, `earningsTime` is `before_open`, `after_close`, `during_market`, or `unknown`. An absent `earningsTime` means no timing value is available, distinct from the explicit string `unknown`; the separate reaction-history field `timing` is absent from this endpoint.
- A window with no matches returns `200` with its real `windowStart` and `windowEnd`, `totalInWindow: 0`, and empty `rows`.

---

## Earnings Calendar

The Earnings Calendar answers who reports and when. It is a forward-looking feed of scheduled company earnings, each carrying the report date, session timing (before open or after close), fiscal quarter, whether the company has confirmed the date, and the consensus EPS estimate. Use it to build a pre-earnings watchlist or to tell a research agent when a thesis has an upcoming catalyst. FREE keys see the first week of the requested window; PRO keys see the full forward window (about 60 days), with identical field richness across tiers.

Two headline query shapes:
- `GET /api/v1/calendar/earnings?week=next` for the companies reporting next week (or `?from=&to=` for a custom window).
- `GET /api/v1/calendar/earnings?ticker=AAPL` for a single name's next report date and consensus EPS.

Full reference: [Calendar API](/docs/api/calendar).

---

## Reported Financials and KPIs

Once a company reports, pull the reported numbers. The Stocks API serves standardized financial-statement data and curated company KPIs.

- **Financial statements:** `GET /api/v1/stocks/fundamentals` (income statement, balance sheet, cash flow), with `GET /api/v1/stocks/fundamentals/current`, `GET /api/v1/stocks/fundamentals/periods`, and `GET /api/v1/stocks/fundamentals/historical/revenue`.
- **Company KPIs:** `GET /api/v1/stocks/{ticker}/kpis` returns curated GAAP and non-GAAP metrics extracted from earnings filings and press releases (for example segment and product revenue lines). KPIs are a **PRO preview**: FREE and unauthenticated callers receive metadata only with an empty `kpis` list; PRO returns the full time series. Enumerate coverage with `GET /api/v1/stocks/with-kpis`.

Full reference: [Stocks API](/docs/api/stocks).

---

## Earnings Insights

With the numbers and the filing changes in hand, the Insights API surfaces the takeaway as AI-generated signals. Alongside insider, institutional, sentiment, and volume signal types, it can carry earnings-driven insight types such as `earnings_pulse`.

**What `earnings_pulse` is:** a short editorial takeaway on a quarter a company has already reported, in the standard insight shape (`insightText`, `category`, `confidence`, `urgency`, `generatedAt`). It is a signal, not a report.

**Treat it as opportunistic, not guaranteed.** Insights of this kind are editorial and time-boxed: they surface around an earnings event while the read is fresh, then expire. Outside those windows a ticker can legitimately return no `earnings_pulse` rows at all, and there is no promise of one per quarter, per ticker, or per season. Build for the empty case, and use the per-quarter analysis report for the parts of a quarter you need on every call.

**What it is not:** it is not the per-quarter earnings digest. For the headline, KPI cards with year-over-year deltas, guidance language, and the summary of the earnings call, use the earnings analysis report above (`GET /api/v1/stocks/{ticker}/earnings-summaries`). Pair it with consensus EPS from the Calendar for the beat-or-miss anchor, standardized statements from `GET /api/v1/stocks/fundamentals`, and curated metrics from `GET /api/v1/stocks/{ticker}/kpis`.

Discover which types have data for a ticker with `GET /api/v1/insights/stock/{ticker}/types`, then filter the main feed by `insightType`. Every type on that list has at least one currently servable insight, so `earnings_pulse` missing from it means there is nothing live for that ticker right now.

Full reference: [Insights API](/docs/api/insights).

Related: [Calendar API](/docs/api/calendar) for upcoming report dates, session timing and consensus EPS estimates.
