> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brew.new/llms.txt
> Use this file to discover all available pages before exploring further.

# Read Brew Insights

> List the findings Brew Insights keeps about your brand's email, check how fresh they are, and read one in full with its frozen metrics.

Brew Insights watches the brand's email and records what it finds:
deliverability problems, emails that did better or worse than usual,
timing, and audience and automation opportunities. In the app, the
**Brew Insights** cards on **Home** open the Insights page. Over the API,
`GET /v1/insights` lists the findings as that page ranks them, and
`GET /v1/insights/{insightId}` reads one in full.

Both reads are free, need the `emails` scope, and count against the
`analytics.read` [rate limit](/api-reference/api/rate-limits). An
organization key names the brand with `X-Brand-Id`, as on every
brand-scoped route.

## List the Findings

<CodeGroup>
  ```bash curl theme={null}
  curl -G "https://brew.new/api/v1/insights" \
    -H "Authorization: Bearer $BREW_API_KEY" \
    --data-urlencode "severity=critical"
  ```

  ```ts SDK theme={null}
  import { createBrewClient } from '@brew.new/sdk'

  const brew = createBrewClient({ apiKey: process.env.BREW_API_KEY! })

  const page = await brew.insights.list({ severity: 'critical' })
  for (const finding of page.data) {
    console.log(finding.severity, finding.title, finding.description)
  }
  ```

  ```bash CLI theme={null}
  brew-cli insights list --severity critical
  ```
</CodeGroup>

It answers `200` with `{ data, pagination, freshness }`, most severe first
and then by score:

```json theme={null}
{
  "data": [
    {
      "insightId": "k17a8m2v4w5x6y7z8a9b0c1d2e3f4g5h",
      "title": "Spring sale",
      "description": "Spring sale bounced 6.2% of sends, above the 6% line.",
      "severity": "critical",
      "confidence": "high",
      "kind": "insight",
      "category": "deliverability",
      "detectorId": "deliv.bounce_rate_breach",
      "state": "active",
      "firstSeenAt": "2026-10-01T12:00:00.000Z",
      "lastSeenAt": "2026-10-02T06:04:12.000Z",
      "recurrenceCount": 1,
      "action": {
        "kind": "navigate",
        "label": "Open campaign analytics",
        "url": "https://brew.new/analytics/sends/Vx2mZ8t9QbY3sW1vR0pLd"
      },
      "url": "https://brew.new/insights/k17a8m2v4w5x6y7z8a9b0c1d2e3f4g5h"
    }
  ],
  "pagination": { "limit": 100, "cursor": null, "hasMore": false },
  "freshness": {
    "dataAsOf": "2026-10-02T06:00:00.000Z",
    "lastSuccessfulRunAt": "2026-10-02T06:04:12.000Z",
    "latestAttempt": { "status": "succeeded", "at": "2026-10-02T06:04:12.000Z" }
  }
}
```

`description` is the detector's own headline. Quote it rather than restating
its numbers. `kind` says what sort of finding it is: `answer` reports what
happened, `insight` is a change that passed its statistical test, and
`strategy` is a recommended next step. `action`, when present, either links
to a page in Brew (`navigate`) or carries the request the Insights page
would send to Brew's assistant (`assistant`, with `intent` and `prompt`).

| Query | Values | What it does |
| - | - | - |
| `state` | `open` (default), `all` | `open` lists active findings and snoozes that have ended. `all` adds resolved, cleared, dismissed, and stale findings, and snoozes still in effect. |
| `severity` | `critical`, `warning`, `opportunity`, `info` | Keeps one severity, up to 200 findings of its own however many more severe ones exist. |
| `include` | `pulse`, `report`, `suggestions`, `memo` | Comma-separated expansions. See [Add the Intelligence Layer](#add-the-intelligence-layer). |
| `limit`, `cursor` | 1 to 100 (default 100) | Page through the list. The engine keeps at most 200 findings in view, so at the default limit a full walk is at most two pages. See [When the Findings Change Mid-Walk](#when-the-findings-change-mid-walk). |

### When the Findings Change Mid-Walk

The list is ranked again on every request, so it can change between pages:
the engine runs, or someone snoozes or dismisses a finding. The cursor
remembers which findings it has already returned.

* **A change below the cursor is served.** A finding added further down the
  list, or one that leaves it before you reach it, is simply on (or off) its
  page when that page comes.
* **A change above the cursor refuses the next page.** If a finding is added
  above the cursor, one you already have leaves the list, or a finding is
  re-ranked across the cursor, the next page would skip or repeat findings.
  It answers `400 INVALID_REQUEST` with `param: "cursor"`: "The findings
  changed since this cursor was issued, so its next page would skip or repeat
  some." Read the list again from the first page, without `cursor`, with
  the same `state` and `severity`. Dropping them starts a different list.

So a walk that reaches its last page has returned every finding exactly
once, as the list stands at that last page.

A cursor also carries the `state` and `severity` that returned it. Sending it
with a different `state` or `severity` is `400 INVALID_REQUEST` with
`param: "cursor"`. Pass the filters that returned it, or start over from
the first page without `cursor`.

In the SDK, both refusals throw `BrewApiError` with `code` `INVALID_REQUEST`
and `param` `cursor`. `brew-cli insights list --all` starts over from the
first page once when the findings change during its walk.

## Check Freshness Before You Report

Every page carries `freshness`, whatever you filter on:

* `dataAsOf` is how current the data behind the findings is. It is `null`
  before the first successful run.
* `lastSuccessfulRunAt` is when the engine last finished a run.
* `latestAttempt` is the newest run and its `status`: `succeeded`, `running`,
  `failed`, or `unknown`. A `failed` attempt means the findings may be stale,
  so say so when you report them.

## Add the Intelligence Layer

`include` adds what the Insights page shows beside the findings. Each
expansion is a top-level key on the page. `pulse`, `report`, and `memo` are
`null` until they exist, and `suggestions` is an empty array when there are
none.

| Token | Key | What it holds |
| - | - | - |
| `pulse` | `pulse` | The last 7 days against the 7 before: delivered, unique opens and clicks, and unsubscribes, each with its prior value, plus open and click rates. `openDirection` and `clickDirection` read `up` or `down` only when the change is statistically real. `countsOnly: true` means too few deliveries for rates, and `measured: false` means engagement tracking is off. |
| `report` | `report` | The latest intelligence report the analysis agent published: `reportId`, the `chatId` that wrote it, `runTrigger` (`manual` or `scheduled`), and its `insights`, each with a `title`, `body`, `impact`, and the `suggestionId` it proposes. |
| `suggestions` | `suggestions` | Proposed and launched suggestions, up to 25, most recently updated first. Each has a `prompt` (exactly what launching it asks Brew to do), `status`, `rationale`, and the `executionChatId` a launched one runs in. |
| `memo` | `memo` | The analysis agent's memory across runs, as `markdown` of at most 8,192 bytes, with its `version` and `updatedAt`. |

The expansions describe the brand, not the page, so they are the same on
every page. Ask for them on the first page only.

<CodeGroup>
  ```bash curl theme={null}
  curl -G "https://brew.new/api/v1/insights" \
    -H "Authorization: Bearer $BREW_API_KEY" \
    --data-urlencode "include=pulse,report,suggestions,memo"
  ```

  ```ts SDK theme={null}
  const page = await brew.insights.list({
    include: ['pulse', 'report', 'suggestions', 'memo'],
  })
  if (page.pulse?.measured && !page.pulse.countsOnly) {
    console.log(`Opens ${page.pulse.openDirection}: ${page.pulse.openRatePct}%`)
  }
  ```

  ```bash CLI theme={null}
  brew-cli insights list --include pulse,report,suggestions,memo
  ```
</CodeGroup>

## Read One Finding

Pass the `insightId` a list row carries.

<CodeGroup>
  ```bash curl theme={null}
  curl "https://brew.new/api/v1/insights/k17a8m2v4w5x6y7z8a9b0c1d2e3f4g5h" \
    -H "Authorization: Bearer $BREW_API_KEY"
  ```

  ```ts SDK theme={null}
  const finding = await brew.insights.get('k17a8m2v4w5x6y7z8a9b0c1d2e3f4g5h')
  console.log(finding.method?.resolvesWhen)
  ```

  ```bash CLI theme={null}
  brew-cli insights get k17a8m2v4w5x6y7z8a9b0c1d2e3f4g5h
  ```
</CodeGroup>

The answer is the bare finding: the list row's fields plus these.

| Field | What it holds |
| - | - |
| `metrics` | The numbers behind the finding, keyed by name and frozen when it was computed. Each has a `kind` (`count`, `rate`, `share`, `duration`, `delta`, `hourOfDay`, `multiple`, `rank`, `interval`, or `label`) that says how to read it. These are the only numbers to quote about the finding. |
| `evidence` | Links that support it, each `{ label, url? }`. |
| `subject` | What it is about: `{ kind, id, label }`, such as one send. |
| `rationale` | The reasoning behind it, or `null`. |
| `method` | How the detector works: `what` it measures, `how`, what it is `comparedAgainst`, what `resolvesWhen`, and an optional `caveat`. |
| `generatedBy` | The engine run that produced it: its `trigger`, when it started and completed, and `blindSpots`, what that run could not see. |
| `closedReason`, `closedAt`, `lastActedAt`, `churnCount` | Its lifecycle: why and when it closed, when someone last marked it acted on, and how often it has closed and reopened. Acting on a finding doesn't close it; it resolves when the detector sees the problem recover. |
| `freshness` | The same freshness block the list carries. |

## Errors

| Answer | Why | What to do |
| - | - | - |
| [`400 INVALID_REQUEST`](/api-reference/api/errors#invalid-request) | An unknown query key, `state`, `severity`, or `include` token, or a malformed `cursor` | Fix the request; `param` names the field |
| `400 INVALID_REQUEST`, `param: "cursor"` | The findings changed above the cursor since it was issued, or the cursor came from a different `state` or `severity` | Read the list again from the first page, without `cursor`, with the same `state` and `severity` |
| [`403 INSUFFICIENT_PERMISSIONS`](/api-reference/api/errors#insufficient-permissions) | The key has no `emails` scope | Use a key with `emails` |
| [`404 INSIGHT_NOT_FOUND`](/api-reference/api/errors#insight-not-found) | No finding with that id for this brand. An unknown id, a malformed one, and another brand's finding all answer the same way. | Take the id from `GET /v1/insights` on the same brand |

## From an Agent

Over [MCP](/api-reference/mcp/tools), `list_insights` and `get_insight` read
the same findings, and `include` is an array (`include: ["pulse", "report"]`).
`list_insights` reads 10 findings per page. To fit one tool result, it
shortens long text on each finding (a shortened field ends in "…") and cuts
the report, suggestions, and memo where its budget ends. A `truncated` array
names each cut section as `{ section, shown, total }`. For `report` and
`suggestions`, `shown` and `total` count items: the report's insights, or the
suggestions. For `memo` they count characters of its `markdown`. The HTTP
read has no such limit and always answers whole. A stale cursor or one from
another filter comes back as a tool error naming `cursor`, the same refusal
the HTTP read gives.

From the shell, [`brew-cli insights list`](/api-reference/cli/commands) takes
`--state`, `--severity`, and `--include`, and `--all` returns every finding
with the expansions from the first page.

## See Also

* [List insights](/api-reference/public-v1/insights/list-insights) and [Get an insight](/api-reference/public-v1/insights/get-an-insight) in the Public API v1 reference: every field and its schema.
* [Pagination](/api-reference/api/pagination): the cursor envelope and the MCP page sizes.
* [Errors](/api-reference/api/errors): the error envelope and every code.
* [Reading Analytics](/analytics/reading-analytics): the sends and events behind the findings.

## Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

<Tabs>
  <Tab title="Self-Service Tools">
    <CardGroup cols="2">
      <Card title="Search Documentation" icon="magnifying-glass" color="#c44925">
        Type in the "Ask any question" search bar at the top left to instantly find relevant documentation pages.
      </Card>

      <Card title="ChatGPT/Claude Integration" icon="robot" color="#c44925">
        Click "Open in ChatGPT" at the top right of any page to explore it further with ChatGPT or Claude.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Talk to Our Team">
    <CardGroup cols="2">
      <Card title="Schedule a Call" icon="calendar" color="#c44925" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1iYoRUG1J792XQpbuQLjSRRDupr7MwraFK-HQRCtTYdBmrQi8nZu2qXfzKQigb8gbKJK3KN3-R">
        Book time with our founders for personalized guidance on strategy, best practices, or complex implementation questions.
      </Card>

      <Card title="Call Us Directly" icon="phone" color="#c44925">
        Need immediate assistance? Reach us at **+1-(332)-203-2145** for urgent issues or time-sensitive questions.
      </Card>

      <Card title="Slack Channel" icon="slack" color="#c44925">
        Our preferred support channel. You'll receive an invite after signup for direct founder support and fast responses.
      </Card>

      <Card title="Email Support" icon="envelope" color="#c44925" href="mailto:support@brew.new">
        Contact us at **[support@brew.new](mailto:support@brew.new)** for detailed inquiries or if you prefer not to use Slack.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.