---
title: Discover
api: POST /brands/{brand_id}/discover
description: "Build the keyword universe, collect evidence, and synthesize scored opportunities."
---

> **For AI agents:** the complete documentation index is at [llms.txt](https://docs.boringmarketing.com/llms.txt). Append `.md` to any page URL for its markdown version.

Runs family discovery, evidence collection, and opportunity synthesis as a mini-pipeline. Produces your keyword universe, evidence, and ranked opportunities — but does **not** run brand enrichment, competitor discovery, leverage scoring, or queue building.

**Duration:** 10-20 minutes.

<Note>
Unlike `/analyze`, this endpoint does **not** emit pipeline stage updates — the run's `meta.current_stage` will be empty throughout. If you need stage-level visibility, use `POST /brands/{id}/analyze` instead.

It also does not run `leverage_scoring` or `queue_building`, so `GET /brands/{id}/queue` will not refresh until you follow up with `POST /brands/{id}/score`.
</Note>

## Path parameters

<ParamField path="brand_id" type="string" required>
  The brand UUID.
</ParamField>

## Response (202 Accepted)

```json
{
  "run_id": "run-uuid",
  "status": "queued"
}
```

After completion, results are available via:
- `GET /brands/{id}/families` — keyword universe
- `GET /brands/{id}/evidence` — SERP and citation evidence
- `GET /brands/{id}/opportunities` — ranked opportunities

## Example

```bash
curl -X POST \
  -H "X-API-Key: $BM_API_KEY" \
  https://api.boringmarketing.com/brands/$BRAND_ID/discover
```

<Note>
Use this instead of `/analyze` when you want to skip brand enrichment or when you've already run enrichment separately.
</Note>

## Errors

| Status | Meaning |
|---|---|
| `404` | Brand not found, or not owned by the caller. |
| `409` | Returned when **(a)** another `discovery` run is already active for this brand, or **(b)** a `full` (`/analyze`) run is currently writing to all tables. Other partial runs (e.g. `brand_enrichment`, `technical_enrichment`) do **not** block this endpoint. Response carries an `X-Running-Run-Id` header with the existing run's UUID. Active runs older than 15 minutes are auto-expired before the conflict check. |
| `422` | `brand_id` is not a valid UUID. |
