# Read a breakdown's digest

> For the complete documentation index, see [llms.txt](https://www.shortsintel.com/llms.txt). Every docs page is also available as markdown: add .md to its URL. To connect, read [agents.md](https://www.shortsintel.com/agents.md).

GET `/v1/breakdowns/{id}/digest`

A Breakdown's Digest, answered as `GET /v1/research/{id}/digest` answers it.

[Open in Playground](https://www.shortsintel.com/console/playground?op=getBreakdownDigest&id=id_example&as_of=run) (no API Key needed)

## Price

Free, forever. Collection is the only thing charged.

## Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `id` | path | `string` | Required | — | Breakdown id — the id of its Run. |
| `as_of` | query | `string` one of `run`, `latest` | Optional | `run` | Accepted for symmetry with the other research reads, so one basis can be passed to all of them, and validated like theirs. A digest is always served as `run` whichever you pass: it is what a model wrote about the shortlist the research collected, and there is no fresher one to serve. |

## Responses

### 200 OK

The digest, with the context it was computed in. `as_of` always reads `run` here: a digest is what a model wrote about the shortlist the research collected, not a number joined to a snapshot, so there is no fresher one to serve.

object

A research's digest with the intent, keywords and coverage it was computed in — and none of the evidence.

- `digest` object required
  - `summary` string required Two or three sentences answering the intent directly: what is working on this topic right now, across the videos analysed.
  - `themes` array of object required items 1 to 8
    - `name` string required Short label, three or four words.
    - `share` number required value 0 to 1 Fraction of the analysed videos that belong to this theme, 0-1.
    - `description` string required What the videos in this theme have in common.
    - `video_ids` array of string required `video_id` keys (platform:id) of the analysed videos that belong to this theme, exactly as given in the evidence.
  - `hooks` array of object required items at most 12
    - `text` string required The opening line, quoted VERBATIM from the evidence. Never paraphrase, never invent.
    - `video_id` string required `video_id` key (platform:id) of the video this hook is quoted from, exactly as given in the evidence.
    - `hook_type` string required The kind of hook, e.g. question, claim, negation, stat.
    - `why_it_works` string required One sentence.
    - `template` string length at least 1 The verbatim hook with its specifics (product, number, mistake, outcome, audience) replaced by short bracketed slots, every other word kept as spoken, e.g. "Stop [mistake] if you want [outcome]".
    - `lift` object nullable The `lift_tables.hook_type` row for the quoted video's analysed hook type, attached in code (never written by the model). Null when that video has no analysed hook type.
      - `value` string required The analysed hook type, or format + hook type, the row is keyed by.
      - `video_count` integer required
      - `median_views` number required nullable
      - `median_engagement_rate` number required nullable
      - `views_lift` number required nullable
      - `engagement_lift` number required nullable
      - `low_confidence` boolean required
  - `sentiment` object required
    - `positive` number required value 0 to 1
    - `neutral` number required value 0 to 1
    - `negative` number required value 0 to 1
    - `note` string required One sentence on what drives the distribution.
  - `angles` array of object required items 1 to 8
    - `angle` string required A content angle worth trying.
    - `rationale` string required Why the evidence supports it, referencing what was seen.
  - `formulas` array of object items at most 5
    - `name` string required Short label, three to five words.
    - `recipe` string required One line saying how to make it: the format, how it opens, and the order of its beats.
    - `visual_format` string required The visual format, copied exactly as it appears in `lift_tables.visual_format`.
    - `hook_type` string required The hook type, copied exactly as it appears in `lift_tables.hook_type`.
    - `structure` string required The beat structure the example videos share, as short beats joined by arrows, e.g. "problem → demo → result → CTA".
    - `video_ids` array of string required Up to 3 `video_id` keys (platform:id) of analysed videos that follow this formula, best performing first, exactly as given in the evidence.
    - `lift` object required nullable The `lift_tables.format_hook` row for this formula's visual format and hook type, attached in code (never written by the model): its `video_count` and lifts. The lift is for the format + hook type pair only; the structure is not bucketed. Null when no analysed video has that pair.
      - `value` string required The analysed hook type, or format + hook type, the row is keyed by.
      - `video_count` integer required
      - `median_views` number required nullable
      - `median_engagement_rate` number required nullable
      - `views_lift` number required nullable
      - `engagement_lift` number required nullable
      - `low_confidence` boolean required
  - `gaps` array of object items at most 5 Up to 5 things the audience asks for in the top 10 videos' comments that no analysed video addresses, each with verbatim quoted comments (and the video they came from) and a suggested angle. Empty when no comments were available. Absent on research recorded before gaps existed.
    - `ask` string required What the audience asks for or complains about, in one sentence, in your own words.
    - `comments` array of object required items 1 to 3
      - `text` string required A comment quoted VERBATIM from <comments>, character for character. Never paraphrase, never invent.
      - `video_id` string required `video_id` key (platform:id) of the video this comment was left under, exactly as given in <comments>.
    - `angle` string required A video someone could make that answers this ask, in one sentence.
- `digest_version` integer required
- `lift_tables` object Lift tables computed in code over the analysed videos: per visual format, hook type and length bucket, the video count, median views and engagement rate, and lift versus the whole set's median. The Digest quotes lifts only from these. Absent on research recorded before they existed.
  - `basis` string one of `set_median`, `creator_median`
  - `set` object required
    - `video_count` integer required
    - `median_views` number required nullable
    - `median_engagement_rate` number required nullable
  - `visual_format` array of object required
    - `value` string required The visual format, hook type, length bucket, or `<format> + <hook type>`.
    - `video_count` integer required
    - `median_views` number required nullable
    - `median_engagement_rate` number required nullable (likes + comments + shares + saves) / views.
    - `views_lift` number required nullable Median views of this row over the whole set's median views. Null when the set median is 0 or unknown.
    - `engagement_lift` number required nullable
    - `low_confidence` boolean required True when the row rests on fewer than 3 videos.
  - `hook_type` array of object required
    - `value` string required The visual format, hook type, length bucket, or `<format> + <hook type>`.
    - `video_count` integer required
    - `median_views` number required nullable
    - `median_engagement_rate` number required nullable (likes + comments + shares + saves) / views.
    - `views_lift` number required nullable Median views of this row over the whole set's median views. Null when the set median is 0 or unknown.
    - `engagement_lift` number required nullable
    - `low_confidence` boolean required True when the row rests on fewer than 3 videos.
  - `length` array of object required
    - `value` string required The visual format, hook type, length bucket, or `<format> + <hook type>`.
    - `video_count` integer required
    - `median_views` number required nullable
    - `median_engagement_rate` number required nullable (likes + comments + shares + saves) / views.
    - `views_lift` number required nullable Median views of this row over the whole set's median views. Null when the set median is 0 or unknown.
    - `engagement_lift` number required nullable
    - `low_confidence` boolean required True when the row rests on fewer than 3 videos.
  - `format_hook` array of object Visual format and hook type together, `value` as `<format> + <hook type>`: the rows the Digest's formulas take their lift from. Absent on research recorded before it existed.
    - `value` string required The visual format, hook type, length bucket, or `<format> + <hook type>`.
    - `video_count` integer required
    - `median_views` number required nullable
    - `median_engagement_rate` number required nullable (likes + comments + shares + saves) / views.
    - `views_lift` number required nullable Median views of this row over the whole set's median views. Null when the set median is 0 or unknown.
    - `engagement_lift` number required nullable
    - `low_confidence` boolean required True when the row rests on fewer than 3 videos.
- `mode` string one of `keywords`, `creators` What the research was seeded with: `keywords` (an intent) or `creators` (a breakdown). Absent means `keywords`.
- `intent` string
- `keywords` array of string
- `keyword_rationale` string
- `coverage` map of string required
- `as_of` string required one of `run`, `latest` Which snapshots the numbers come from: `run` is what the research itself saw, frozen, so a read always agrees with the digest; `latest` is the newest snapshot this API holds for each video.

200 · The digest, with the context it was computed in. `as_of` always reads `run` here: a digest is what a model wrote about the shortlist the research collected, not a number joined to a snapshot, so there is no fresher one to serve.

```json
{
  "digest": {
    "summary": "Cold plunge videos are travelling on measured personal diaries rather than on science explainers. The videos that break out open on an unimpressive first number and pay it off with a later one, and keep the discomfort on camera instead of cutting around it. Sentiment is broadly positive, with the negative share concentrated on the cost of equipment.",
    "themes": [
      {
        "name": "30-day diaries",
        "share": 0.45,
        "description": "One creator, one tub, a counter on screen, and the change stated as a number rather than a feeling.",
        "video_ids": [
          "tiktok:7300000000000000000"
        ]
      },
      {
        "name": "Before-workout plunging",
        "share": 0.2,
        "description": "Shorter clips arguing about timing relative to training, usually with a gym setting.",
        "video_ids": [
          "tiktok:7300000000000000042"
        ]
      }
    ],
    "hooks": [
      {
        "text": "Day one I lasted eleven seconds.",
        "video_id": "tiktok:7300000000000000000",
        "hook_type": "stat",
        "why_it_works": "An unimpressive concrete number sets up a payoff the viewer stays for.",
        "template": "Day one I lasted [small number] [unit].",
        "lift": {
          "value": "STORY",
          "video_count": 9,
          "median_views": 1840000,
          "median_engagement_rate": 0.0912,
          "views_lift": 1.6,
          "engagement_lift": 1.21,
          "low_confidence": false
        }
      }
    ],
    "sentiment": {
      "positive": 0.62,
      "neutral": 0.23,
      "negative": 0.15,
      "note": "Enthusiasm for the routine itself; the negative share is almost entirely the price of a tub."
    },
    "angles": [
      {
        "angle": "Open on the day-30 number and work backwards to day one.",
        "rationale": "Every diary in the shortlist that led with the outcome outperformed the ones that led with the method."
      }
    ],
    "gaps": [
      {
        "ask": "Viewers want a cold plunge setup that does not cost hundreds.",
        "comments": [
          {
            "text": "ok but what do I do if I can't afford a 600 dollar tub",
            "video_id": "tiktok:7300000000000000000"
          }
        ],
        "angle": "A 30-day diary on a chest freezer or bathtub build, with the receipt on screen."
      }
    ]
  },
  "digest_version": 2,
  "mode": "keywords",
  "intent": "why are cold plunge videos taking off",
  "keywords": [
    "cold plunge",
    "ice bath",
    "cold exposure",
    "cold plunge routine"
  ],
  "keyword_rationale": "The intent is about a practice rather than a product, so the keywords cover the practice and its two common synonyms.",
  "coverage": {
    "tiktok": "complete",
    "instagram": "partial"
  },
  "as_of": "run"
}
```

### 400 Bad Request

`invalid_request` — an unknown `as_of`.

Code: `invalid_request`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 401 Unauthorized

Codes: `missing_authorization`, `invalid_key`, `key_revoked`, `key_expired`, `invalid_token`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 404 Not Found

Code: `not_found`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 409 Conflict

`run_not_ready` — the research is still running, or failed with nothing to serve. If it is still running, poll `GET /v1/runs/{id}` and read again once it settles; a failed research never becomes readable.

Code: `run_not_ready`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

## Request samples

curl:

```bash
curl "https://www.shortsintel.com/v1/breakdowns/id_example/digest?as_of=run" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

TypeScript:

```typescript
const response = await fetch(
  "https://www.shortsintel.com/v1/breakdowns/id_example/digest?as_of=run",
  {
    method: "GET",
    headers: {
      "Authorization": "Bearer sk_live_YOUR_KEY",
    },
  },
);

if (!response.ok) {
  throw new Error(`ShortsIntel ${response.status}`);
}

const data = await response.json();
```

Python:

```python
import requests

response = requests.get(
    "https://www.shortsintel.com/v1/breakdowns/id_example/digest?as_of=run",
    headers={
        "Authorization": "Bearer sk_live_YOUR_KEY",
    },
)
response.raise_for_status()

data = response.json()
```

[Previous Rank a breakdown's sounds](https://www.shortsintel.com/docs/reference/breakdowns/list-breakdown-sounds)[Next Refresh a breakdown's videos](https://www.shortsintel.com/docs/reference/breakdowns/refresh-breakdown)
